conjur-cli 4.18.6 → 4.19.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48872bafebcf0d1adecc8364f839bd7318839c14
4
- data.tar.gz: 523c5883db7eb7faf4afad5f31543e8629490685
3
+ metadata.gz: 88cb451c93417c3943171c8948b0b8fca26e9faf
4
+ data.tar.gz: fec1d704173532741c53bfe76b81dd71286fe8d7
5
5
  SHA512:
6
- metadata.gz: e00e1a4898d768816840ee41887b5ed59f6d3fa68d3bf1677ff0ce9388c88bb844b476581bf64e076b903b5fe6c7822047f30e68416d7fe789da7afbc85928aa
7
- data.tar.gz: 018110603af0cda168cc4bc7d525fd12cf8e3332135abd2c73013bfaa7cacbf27ca471e8ae1b3ef83dc22a5daaf3faab144fb0c074c14a291af0bbdf2856f0b9
6
+ metadata.gz: bdb26d0ec021d83bcdbcf176a6899584b0db21a010c841b3416ab58e3d105a3f89d3ce6591563d6d3869543cb5c58cb3716a0561277e35a72facfb02ecbac86e
7
+ data.tar.gz: a99ecb30867ea4090bc660b6836dc7ff19a95ddc2517fe20aafb7502f25c8dc481d8640dc5f0d59f1033b3414ce7b21b2b2a6f49a211da37dc9e702e8872c318
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 4.19.0
2
+
3
+ * Add command `conjur role graph` for batch retrieval of role relationships
4
+
1
5
  # 4.18.5
2
6
 
3
7
  * Bump conjur-api version to mime-types problem
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ Cucumber::Rake::Task.new :features
11
11
 
12
12
  task :jenkins => ['ci:setup:rspec', :spec, 'ci:setup:cucumber_report_cleanup'] do
13
13
  Cucumber::Rake::Task.new do |t|
14
- t.cucumber_opts = "--format progress --format CI::Reporter::Cucumber --out features/reports"
14
+ t.cucumber_opts = "--tags ~@real-api --format progress --format CI::Reporter::Cucumber --out features/reports"
15
15
  end.runner.run
16
16
  File.write('build_number', ENV['BUILD_NUMBER']) if ENV['BUILD_NUMBER']
17
17
  end
data/conjur.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
 
18
18
 
19
19
  gem.add_dependency 'activesupport'
20
- gem.add_dependency 'conjur-api', '~> 4.11.2'
20
+ gem.add_dependency 'conjur-api', '~> 4.12.0'
21
21
  gem.add_dependency 'gli', '>=2.8.0'
22
22
  gem.add_dependency 'highline'
23
23
  gem.add_dependency 'netrc', '~> 0.10.2'
@@ -33,4 +33,5 @@ Gem::Specification.new do |gem|
33
33
  gem.add_development_dependency 'ci_reporter_cucumber'
34
34
  gem.add_development_dependency 'rake', '~> 10.0'
35
35
  gem.add_development_dependency 'io-grab', '~> 0.0.1'
36
+ gem.add_development_dependency 'json_spec'
36
37
  end
@@ -0,0 +1,58 @@
1
+ @real-api
2
+ Feature: Retrieving role graphs
3
+ As a Conjur user
4
+ In order to understand the role hierarchy
5
+ I want to retrieve role graphs and present them in a useful format
6
+
7
+ Background:
8
+ Given a graph with edges
9
+ | Tywin | Jamie |
10
+ | Tywin | Cersei |
11
+ | Cersei | Joffrey |
12
+ | Jamie | Joffrey |
13
+ | Aerys | Tyrion |
14
+ | Joanna | Tyrion |
15
+
16
+ Scenario: Showing the graph as JSON
17
+ When I successfully run with role expansion "conjur role graph --as-role Joffrey Joffrey"
18
+ Then the graph JSON should be:
19
+ """
20
+ {
21
+ "graph": [
22
+ { "parent": "Tywin", "child": "Jamie" },
23
+ { "parent": "Tywin", "child": "Cersei"},
24
+ { "parent": "Cersei", "child": "Joffrey"},
25
+ { "parent": "Jamie", "child": "Joffrey" }
26
+ ]
27
+ }
28
+ """
29
+
30
+ Scenario: Short JSON output
31
+ When I successfully run with role expansion "conjur role graph --short --as-role Joffrey Joffrey"
32
+ Then the graph JSON should be:
33
+ """
34
+ [
35
+ [ "Tywin", "Jamie" ],
36
+ [ "Tywin", "Cersei" ],
37
+ [ "Jamie", "Joffrey" ],
38
+ [ "Cersei", "Joffrey"]
39
+ ]
40
+ """
41
+
42
+ Scenario: I can restrict the output to show only ancestors or descendants
43
+ When I successfully run with role expansion "conjur role graph --short --no-ancestors --as-role Cersei Cersei"
44
+ Then the graph JSON should be:
45
+ """
46
+ [
47
+ [ "Cersei", "Joffrey" ]
48
+ ]
49
+ """
50
+ When I successfully run with role expansion "conjur role graph --short --no-descendants --as-role Cersei Cersei Jamie"
51
+ Then the graph JSON should be:
52
+ """
53
+ [
54
+ [ "Tywin", "Cersei" ],
55
+ [ "Tywin", "Jamie" ]
56
+ ]
57
+ """
58
+
@@ -0,0 +1,21 @@
1
+ Given /a graph with edges/ do |table|
2
+ graph table.raw
3
+ end
4
+
5
+ Then %r{the graph JSON should be} do |json|
6
+ json = expand_roles json
7
+ last_graph = extract_filtered_graph json
8
+ expect(last_graph.to_json).to be_json_eql(json)
9
+ end
10
+
11
+ When(/^I( successfully)? run with role expansion "(.*)"$/) do |successfully, cmd|
12
+ role_id_map.each do |role, expanded_role|
13
+ cmd.gsub! role, expanded_role
14
+ end
15
+ self.last_cmd = cmd
16
+ if successfully
17
+ step "I successfully run \"#{cmd}\""
18
+ else
19
+ step "I run \"#{cmd}\""
20
+ end
21
+ end
@@ -2,5 +2,7 @@ require 'simplecov'
2
2
  require 'aruba/cucumber'
3
3
  require 'methadone/cucumber'
4
4
  require 'cucumber/rspec/doubles'
5
+ require "json_spec/cucumber"
6
+
5
7
 
6
8
  SimpleCov.start
@@ -2,17 +2,17 @@ require 'ostruct'
2
2
 
3
3
  class MockAPI
4
4
  attr_reader :things
5
-
5
+
6
6
  def initialize
7
7
  @things = {}
8
8
  end
9
-
9
+
10
10
  def thing(kind, id)
11
- (@things[kind.to_sym] || []).find{|r| r.id == id}
11
+ (@things[kind.to_sym] || []).find{|r| r.id == id}
12
12
  end
13
-
13
+
14
14
  def thing_like(kind, id_pattern)
15
- (@things[kind.to_sym] || []).find{|r| id_pattern.match(r.id)}
15
+ (@things[kind.to_sym] || []).find{|r| id_pattern.match(r.id)}
16
16
  end
17
17
 
18
18
  def create_host(options = {})
@@ -24,56 +24,56 @@ class MockAPI
24
24
  end
25
25
  host ||= create_thing(:host, id, options, role: true, api_key: true)
26
26
  end
27
-
27
+
28
28
  def create_user(id, options = {})
29
29
  thing(:user, id) || create_thing(:user, id, options, role: true, api_key: true)
30
30
  end
31
-
31
+
32
32
  def create_variable(mime_type, kind)
33
33
  create_thing(:user, SecureRandom.uuid, mime_type: mime_type, kind: kind)
34
34
  end
35
-
35
+
36
36
  def create_resource(id, options = {})
37
37
  resource(id).tap do |resource|
38
38
  resource.send(:"exists?=", true)
39
39
  populate_options resource, options
40
40
  end
41
41
  end
42
-
42
+
43
43
  def create_role(id, options = {})
44
44
  role(id).tap do |role|
45
45
  role.send(:"exists?=", true)
46
46
  populate_options role, options
47
47
  end
48
48
  end
49
-
49
+
50
50
  [ :user, :host ].each do |kind|
51
51
  define_method kind do |id|
52
52
  thing(kind, id)
53
53
  end
54
54
  end
55
-
55
+
56
56
  def role(id)
57
57
  raise "Role id must be a string" unless id.is_a?(String)
58
58
  thing(:role, id) || create_thing(:role, id, { exists?: false }, role: true)
59
59
  end
60
-
60
+
61
61
  def resource(id)
62
62
  raise "Resource id must be a string" unless id.is_a?(String)
63
63
  thing(:resource, id) || create_thing(:resource, id, exists?: false)
64
64
  end
65
-
65
+
66
66
  protected
67
-
67
+
68
68
  def create_thing(kind, id, options, kind_options = {})
69
69
  thing = OpenStruct.new(kind: kind, id: id, exists?: true)
70
-
70
+
71
71
  class << thing
72
72
  def permit(privilege, role, options = {})
73
73
  (self.permissions ||= []) << OpenStruct.new(privilege: privilege, role: role.id, grant_option: !!options[:grant_option])
74
74
  end
75
75
  end
76
-
76
+
77
77
  if kind_options[:api_key]
78
78
  thing.api_key = SecureRandom.uuid
79
79
  end
@@ -85,20 +85,20 @@ class MockAPI
85
85
  end
86
86
  end
87
87
  end
88
-
88
+
89
89
  populate_options(thing, options)
90
-
90
+
91
91
  store_thing kind, thing
92
-
92
+
93
93
  thing
94
94
  end
95
-
95
+
96
96
  def populate_options(thing, options)
97
97
  options.each do |k,v|
98
98
  thing.send("#{k}=", v)
99
99
  end
100
100
  end
101
-
101
+
102
102
  def store_thing(kind, thing)
103
103
  (things[kind] ||= []) << thing
104
104
  end
@@ -107,11 +107,11 @@ end
107
107
  Before("@dsl") do
108
108
  puts "Using MockAPI"
109
109
  puts "Using account 'cucumber'"
110
-
110
+
111
111
  require 'conjur/api'
112
112
  require 'conjur/config'
113
113
  require 'conjur/dsl/runner'
114
-
114
+
115
115
  Conjur.stub(:env).and_return "ci"
116
116
  Conjur.stub(:stack).and_return "ci"
117
117
  Conjur.stub(:account).and_return "cucumber"
@@ -119,4 +119,14 @@ Before("@dsl") do
119
119
  Conjur::Core::API.stub(:conjur_account).and_return 'cucumber'
120
120
  @mock_api ||= MockAPI.new
121
121
  Conjur::DSL::Runner.any_instance.stub(:api).and_return @mock_api
122
- end
122
+ end
123
+
124
+ Before('@real-api') do
125
+ require 'conjur/config'
126
+ cfg = File.absolute_path("#{File.dirname __FILE__}/../../.conjurrc")
127
+ puts "cfg_path = #{cfg}"
128
+ puts "contents = #{File.read(cfg)}"
129
+ Conjur::Config.load([cfg])
130
+ Conjur::Config.apply
131
+ @aruba_timeout_seconds = 15
132
+ end
@@ -0,0 +1,88 @@
1
+ require 'conjur/api'
2
+ module ConjurWorld
3
+ def last_json
4
+ last_stdout
5
+ end
6
+
7
+ def last_stdout
8
+ raise "No commands have been run" unless last_cmd
9
+ stdout_from last_cmd
10
+ end
11
+
12
+ attr_accessor :last_cmd
13
+
14
+ def account
15
+ Conjur::Core::API.conjur_account
16
+ end
17
+
18
+ def role_kind
19
+ @role_kind ||= "cli-cukes"
20
+ end
21
+
22
+ def role_id_map
23
+ @role_id_map ||= {}
24
+ end
25
+
26
+ def extract_filtered_graph json
27
+ graph = JSON.parse(json) if json.kind_of?(String)
28
+ case graph
29
+ when Hash then filter_hash_graph(graph)
30
+ when Array then filter_array_graph(graph)
31
+ else raise "WTF: graph was #{graph.class}?"
32
+ end
33
+ end
34
+
35
+ def filter_hash_graph graph
36
+ allowed = role_id_map.values
37
+ edges = graph['graph']
38
+ filtered = edges.select do |edge|
39
+ allowed.member?(edge['parent']) and allowed.member?(edge['child'])
40
+ end
41
+ {'graph' => filtered}
42
+ end
43
+
44
+ def filter_array_graph graph
45
+ allowed = role_id_map.values
46
+ graph.select do |edge|
47
+ edge.all?{|v| allowed.member?(v)}
48
+ end
49
+ end
50
+
51
+ def graph edges
52
+ # generate roles
53
+ edges.flatten.uniq.each do |role_id|
54
+ role_id_map[role_id] = expanded = expand_role_id(role_id)
55
+ run_command "conjur role create '#{expanded}'"
56
+ end
57
+
58
+ # generate memberships
59
+ edges.each do |parent, child|
60
+ run_command "conjur role grant_to #{expand_role_id(parent)} #{expand_role_id(child)}"
61
+ end
62
+ end
63
+
64
+ def run_command cmd
65
+ step "I successfully run " + '`' + cmd + '`'
66
+ end
67
+
68
+ def expand_role_id role_id
69
+ "#{account}:#{role_kind}:#{prepend_namespace role_id}"
70
+ end
71
+
72
+ def prepend_namespace id
73
+ "#{namespace}-#{id}"
74
+ end
75
+
76
+ def namespace
77
+ @namespace ||= "ns-#{Time.now.to_i}-#{rand(1 << 32)}"
78
+ end
79
+
80
+ def expand_roles string
81
+ role_id_map.each do |role, expanded|
82
+ string.gsub! role, expanded
83
+ end
84
+ string
85
+ end
86
+ end
87
+
88
+ World(ConjurWorld)
@@ -20,6 +20,8 @@
20
20
  #
21
21
 
22
22
  class Conjur::Command::Roles < Conjur::Command
23
+ GRAPH_FORMATS = %w(json dot)
24
+
23
25
 
24
26
  desc "Manage roles"
25
27
  command :role do |role|
@@ -117,6 +119,7 @@ class Conjur::Command::Roles < Conjur::Command
117
119
  end
118
120
  end
119
121
 
122
+
120
123
  role.desc "Revoke a role from another role. You must have admin permission on the revoking role."
121
124
  role.arg_name "role member"
122
125
  role.command :revoke_from do |c|
@@ -128,6 +131,87 @@ class Conjur::Command::Roles < Conjur::Command
128
131
  puts "Role revoked"
129
132
  end
130
133
  end
131
- end
132
134
 
135
+
136
+ role.long_desc <<-EOD
137
+ Retrieves a digraph representing the role members and memberships of one or more roles.
138
+
139
+ The --[no-]ancestors and --[no-descendants] determine whether the graph should include ancestors, descendants, or both. Both
140
+ are included in the graph by default.
141
+
142
+ The --acting-as flag specifies, as usual, a role as which to perform the action. The default is the role of the currently
143
+ authenticated user. Only roles visible to this role will be included in the resulting graph.
144
+
145
+ The output is always written to the standard output, and can be one of the following forms (specified with the --format flag):
146
+
147
+ * png: use the 'dot' command to generate a png image representing the graph.
148
+
149
+ * dot: produce a file in a suitable format for use with the 'dot' program.
150
+
151
+ * json [default]: output a JSON representation of the graph.
152
+
153
+ In order to generate png images, the 'dot' program must be present and on your path. This program is usually installed
154
+ as part of the 'graphviz' package, and is available via apt-get on debian like systems and homebrew on OSX.
155
+
156
+ The JSON format is determined by the presence of the --short flag. If the --short flag is present, the JSON will be an array of
157
+ edges, with each edge represented as an array:
158
+
159
+ [
160
+ [ 'parent1', 'child1' ],
161
+ [ 'parent2', 'child2'],
162
+ ...
163
+ ]
164
+
165
+ If the --short flag is not present, the JSON output will be more verbose:
166
+
167
+ {
168
+ "graph": [
169
+ {
170
+ "parent": "parent1",
171
+ "child": "child1"
172
+ },
173
+ ...
174
+ ]
175
+ }
176
+ EOD
177
+
178
+ role.desc "Describe role memberships as a digraph"
179
+ role.arg_name "role", :multiple
180
+ role.command :graph do |c|
181
+ c.desc "Output formats [#{GRAPH_FORMATS}]"
182
+ c.flag [:f,:format], default_value: 'json', must_match: GRAPH_FORMATS
183
+
184
+ c.desc "Use a more compact JSON format"
185
+ c.switch [:s, :short]
186
+
187
+ c.desc "Whether to show ancestors"
188
+ c.switch [:a, :ancestors], default_value: true
189
+
190
+ c.desc "Whether to show descendants"
191
+ c.switch [:d, :descendants], default_value: true
192
+
193
+ acting_as_option(c)
194
+
195
+ c.action do |_, options, args|
196
+ format = options[:format].downcase.to_sym
197
+ if options[:short] and format != :json
198
+ $stderr.puts "WARNING: the --short option is meaningless when --format is not json"
199
+ end
200
+
201
+ params = options.slice(:ancestors, :descendants)
202
+ params[:as_role] = options[:acting_as] if options.member?(:acting_as)
203
+
204
+ graph = api.role_graph(args, params)
205
+
206
+ output = case format
207
+ when :json then graph.to_json(options[:short]) + "\n"
208
+ when :dot then graph.to_dot + "\n"
209
+ else raise "Unsupported format: #{format}" # not strictly necessary, because GLI must_match checks it,
210
+ # but might as well?
211
+ end
212
+
213
+ $stdout.write output
214
+ end
215
+ end
216
+ end
133
217
  end
@@ -19,6 +19,6 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module Conjur
22
- VERSION = "4.18.6"
22
+ VERSION = "4.19.0"
23
23
  ::Version=VERSION
24
24
  end
@@ -85,4 +85,63 @@ describe Conjur::Command::Roles, logged_in: true do
85
85
  end
86
86
  end
87
87
  end
88
+
89
+ describe "role graph" do
90
+ let(:roles){ [] }
91
+ let(:options){ { ancestors: true, descendants: true } }
92
+ let(:extra_options){ {} }
93
+ let(:role_graph_args){ [roles, options.merge(extra_options)] }
94
+ let(:graph_edges){ [['a', 'b'], ['b', 'c']] }
95
+ let(:graph){ Conjur::Graph.new graph_edges }
96
+ def output
97
+ JSON::parse(expect{invoke}.to write)
98
+ end
99
+
100
+ before do
101
+ allow(api).to receive(:role_graph).with(*role_graph_args).and_return graph
102
+ end
103
+
104
+ describe_command "role graph foo bar" do
105
+ let(:roles){ %w(foo bar) }
106
+ it "outputs the graph as non-short json" do
107
+ expect(output).to eq(graph.as_json)
108
+ end
109
+ end
110
+
111
+ describe_command 'role graph --short foo' do
112
+ let(:roles){ %w(foo) }
113
+ it 'outputs the graph as short json' do
114
+ expect(output).to eq(graph.as_json(true))
115
+ end
116
+ end
117
+
118
+ describe_command 'role graph --no-ancestors foo' do
119
+ let(:roles){ %w(foo) }
120
+ let(:options){{descendants: true}}
121
+ it "calls role_graph with the expected options and output" do
122
+ expect(output).to eq(graph.as_json(false))
123
+ end
124
+ end
125
+
126
+ describe 'output formats' do
127
+ let(:formatted){ "formatted by #{format_method}" }
128
+ let(:roles){ %w(foo) }
129
+ before do
130
+
131
+ expect_any_instance_of(Conjur::Graph).to receive(format_method).with(any_args).and_return formatted
132
+ end
133
+
134
+ def self.it_formats_the_graph_as method
135
+ let(:format_method){ method }
136
+ it "formats the graph with #{method}" do
137
+ expect((expect{invoke}.to write).chomp).to eq(formatted)
138
+ end
139
+ end
140
+
141
+ describe_command 'role graph -fdot foo' do
142
+ it_formats_the_graph_as :to_dot
143
+ end
144
+ end
145
+
146
+ end
88
147
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.18.6
4
+ version: 4.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafal Rzepecki
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-22 00:00:00.000000000 Z
12
+ date: 2015-01-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ~>
33
33
  - !ruby/object:Gem::Version
34
- version: 4.11.2
34
+ version: 4.12.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ~>
40
40
  - !ruby/object:Gem::Version
41
- version: 4.11.2
41
+ version: 4.12.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: gli
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -221,6 +221,20 @@ dependencies:
221
221
  - - ~>
222
222
  - !ruby/object:Gem::Version
223
223
  version: 0.0.1
224
+ - !ruby/object:Gem::Dependency
225
+ name: json_spec
226
+ requirement: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - '>='
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - '>='
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
224
238
  description:
225
239
  email:
226
240
  - rafal@conjur.net
@@ -258,13 +272,16 @@ files:
258
272
  - features/dsl_role_create.feature
259
273
  - features/dsl_user_create.feature
260
274
  - features/jsonfield.feature
275
+ - features/role_graph.feature
261
276
  - features/step_definitions/conjurize_steps.rb
262
277
  - features/step_definitions/dsl_steps.rb
278
+ - features/step_definitions/graph_steps.rb
263
279
  - features/support/conjur-test.pem
264
280
  - features/support/conjur.conf
265
281
  - features/support/env.rb
266
282
  - features/support/hooks.rb
267
283
  - features/support/host.json
284
+ - features/support/world.rb
268
285
  - lib/conjur.rb
269
286
  - lib/conjur/audit/follower.rb
270
287
  - lib/conjur/authn.rb
@@ -357,13 +374,16 @@ test_files:
357
374
  - features/dsl_role_create.feature
358
375
  - features/dsl_user_create.feature
359
376
  - features/jsonfield.feature
377
+ - features/role_graph.feature
360
378
  - features/step_definitions/conjurize_steps.rb
361
379
  - features/step_definitions/dsl_steps.rb
380
+ - features/step_definitions/graph_steps.rb
362
381
  - features/support/conjur-test.pem
363
382
  - features/support/conjur.conf
364
383
  - features/support/env.rb
365
384
  - features/support/hooks.rb
366
385
  - features/support/host.json
386
+ - features/support/world.rb
367
387
  - spec/authn_spec.rb
368
388
  - spec/command/assets_spec.rb
369
389
  - spec/command/audit_spec.rb