conjur-cli 4.18.6 → 4.19.0

Sign up to get free protection for your applications and to get access to all the features.
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