conjur-cli 4.25.2 → 4.26.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.
@@ -22,15 +22,15 @@ class Conjur::Command::Variables < Conjur::Command
22
22
  desc "Manage variables"
23
23
  command :variable do |var|
24
24
  var.desc "Create and store a variable"
25
- var.arg_name "id [value]"
25
+ var.arg_name "NAME VALUE"
26
26
  var.command :create do |c|
27
- c.arg_name "mime_type"
27
+ c.arg_name "MIME-TYPE"
28
28
  c.flag [:m, :"mime-type"], default_value: 'text/plain'
29
29
 
30
- c.arg_name "kind"
30
+ c.arg_name "KIND"
31
31
  c.flag [:k, :"kind"], default_value: 'secret'
32
32
 
33
- c.arg_name "value"
33
+ c.arg_name "VALUE"
34
34
  c.desc "Initial value, which may also be specified as the second command argument after the variable id"
35
35
  c.flag [:v, :"value"]
36
36
 
@@ -91,21 +91,21 @@ class Conjur::Command::Variables < Conjur::Command
91
91
  end
92
92
 
93
93
  var.desc "Show a variable"
94
- var.arg_name "id"
94
+ var.arg_name "VARIABLE"
95
95
  var.command :show do |c|
96
96
  c.action do |global_options,options,args|
97
- id = require_arg(args, 'id')
97
+ id = require_arg(args, 'VARIABLE')
98
98
  display(api.variable(id), options)
99
99
  end
100
100
  end
101
101
 
102
102
  var.desc "Decommission a variable"
103
- var.arg_name "id"
103
+ var.arg_name "VARIABLE"
104
104
  var.command :retire do |c|
105
105
  retire_options c
106
106
 
107
107
  c.action do |global_options,options,args|
108
- id = require_arg(args, 'id')
108
+ id = require_arg(args, 'VARIABLE')
109
109
 
110
110
  variable = api.variable(id)
111
111
 
@@ -130,10 +130,10 @@ class Conjur::Command::Variables < Conjur::Command
130
130
  var.desc "Access variable values"
131
131
  var.command :values do |values|
132
132
  values.desc "Add a value"
133
- values.arg_name "variable ( value | STDIN )"
133
+ values.arg_name "VARIABLE VALUE"
134
134
  values.command :add do |c|
135
135
  c.action do |global_options,options,args|
136
- id = require_arg(args, 'variable')
136
+ id = require_arg(args, 'VARIABLE')
137
137
  value = args.shift || STDIN.read
138
138
 
139
139
  api.variable(id).add_value(value)
@@ -143,13 +143,13 @@ class Conjur::Command::Variables < Conjur::Command
143
143
  end
144
144
 
145
145
  var.desc "Get a value"
146
- var.arg_name "variable"
146
+ var.arg_name "VARIABLE"
147
147
  var.command :value do |c|
148
148
  c.desc "Version number"
149
149
  c.flag [:v, :version]
150
150
 
151
151
  c.action do |global_options,options,args|
152
- id = require_arg(args, 'variable')
152
+ id = require_arg(args, 'VARIABLE')
153
153
  $stdout.write api.variable(id).value(options[:version])
154
154
  end
155
155
  end
@@ -0,0 +1,263 @@
1
+ #
2
+ # Copyright (C) 2015 Conjur Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation files
6
+ # (the "Software"), to deal in the Software without restriction,
7
+ # including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software,
9
+ # and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #
24
+ require 'conjur/cli'
25
+ require 'shellwords'
26
+
27
+ # Class for generating `conjur` bash completions
28
+ class Conjur::CLI::Complete
29
+ attr_reader :line, :words, :current_word_index, :commands,
30
+ :switch_words, :flag_words, :arg_words, :command_words
31
+ def initialize line, point=nil
32
+ @line = line
33
+ @words = tokenize_cmd @line
34
+ point ||= @line.length
35
+ @current_word_index=(tokenize_cmd @line.slice(0,point)).length-1
36
+ # fix arrays for empty "current word"
37
+ # ie "conjur group list "
38
+ if @line.match(/[ =]$/)
39
+ @words << ''
40
+ @current_word_index += 1
41
+ end
42
+ @commands,
43
+ @switch_words,
44
+ @flag_words,
45
+ @arg_words = parse_command @words, @current_word_index
46
+ @command_words = @commands
47
+ .drop(1)
48
+ .map(&:name)
49
+ .map(&:to_s)
50
+ .unshift('conjur')
51
+ end
52
+
53
+ def current_word offset=0
54
+ @words[@current_word_index + offset]
55
+ end
56
+
57
+ # Generate array of subcommands for which documentation is not hidden
58
+ #
59
+ # @param cmd [Conjur::CLI::Command] the command to search
60
+ # @return [Array] the subcommands
61
+ def subcommands cmd
62
+ cmd.commands.select do |_, c|
63
+ c.nodoc.nil?
64
+ end
65
+ end
66
+
67
+ # Generate array of symbols representing switches for +cmd+ and
68
+ # their aliases
69
+ #
70
+ # @param cmd [Conjur::CLI::Command] the command to search
71
+ # @return [Array] the symbols representing switches and their aliases
72
+ def switches cmd
73
+ cmd.switches.map { |_,switch|
74
+ [switch.name] + (switch.aliases or [])
75
+ }.flatten
76
+ end
77
+
78
+ # Split line according on spaces and after '='
79
+ #
80
+ # @param line [String] to split
81
+ # @return [Array] the substrings
82
+ def tokenize_cmd line
83
+ line.split(/ |(?<==)/)
84
+ end
85
+
86
+ def flag_to_sym flag
87
+ flag.match(/--?([^=]+)=?/)[1].to_sym
88
+ end
89
+
90
+ def parse_command words, current_word_index
91
+ command = Conjur::CLI
92
+ commands = [command]
93
+ switches = []
94
+ flags = []
95
+ arguments = []
96
+ index = 1
97
+ until index >= current_word_index do
98
+ word = words[index]
99
+ case classify_word word, command
100
+ when :switch
101
+ switches.push word
102
+ when :flag
103
+ flags.push [word, words[index+1]]
104
+ index += 1
105
+ when :subcommand
106
+ command = command.commands[word.to_sym]
107
+ commands.push command
108
+ when :argument
109
+ arguments.push word
110
+ end
111
+ index += 1
112
+ end
113
+ return commands, switches, flags, arguments
114
+ end
115
+
116
+ def classify_word word, command
117
+ if word.start_with? '-'
118
+ sym = flag_to_sym word
119
+ if switches(command).member? sym
120
+ :switch
121
+ else
122
+ :flag
123
+ end
124
+ else
125
+ if subcommands(command).has_key? word.to_sym
126
+ :subcommand
127
+ else
128
+ :argument
129
+ end
130
+ end
131
+ end
132
+
133
+ def complete kind
134
+ kind = kind.to_s.downcase.gsub(/[^a-z]/, '')
135
+ case kind
136
+ when 'resource'
137
+ complete_resource
138
+ when 'role'
139
+ complete_role
140
+ when 'file'
141
+ complete_file current_word
142
+ when 'hostname'
143
+ complete_hostname
144
+ else
145
+ complete_resource kind if [
146
+ 'group',
147
+ 'user',
148
+ 'variable',
149
+ 'host',
150
+ 'layer',
151
+ ].member? kind
152
+ end or []
153
+ end
154
+
155
+ # generate completions for the switches and flags of a Conjur::CLI::Command
156
+ #
157
+ # @param cmd [Conjur::CLI::Command] command for which to search for flags
158
+ # and switches
159
+ # @return [Array] completion words
160
+ def complete_flags cmd
161
+ cmd.flags.values.map do |flag|
162
+ candidates = [flag.name]
163
+ candidates += flag.aliases if flag.aliases
164
+ candidates.map do |c|
165
+ "-#{'-' if c.length > 1}#{c}#{'=' if c.length > 1}"
166
+ end
167
+ end + cmd.switches.values.map do |switch|
168
+ candidates = [switch.name]
169
+ candidates += switch.aliases if switch.aliases
170
+ candidates.map do |c|
171
+ "-#{'-' if c.length > 1}#{c}"
172
+ end
173
+ end
174
+ end
175
+
176
+ def complete_args cmd, prev, num_args
177
+ kind=nil
178
+ if prev.start_with? '-'
179
+ flag_name=flag_to_sym prev
180
+ flag = cmd.flags[flag_name]
181
+ desc = flag.argument_name if defined? flag.argument_name
182
+ kind = desc.to_s.downcase
183
+ else
184
+ desc = cmd.arguments_description if defined? cmd.arguments_description
185
+ kind = desc.to_s.downcase.split[num_args-1]
186
+ end
187
+ complete kind
188
+ end
189
+
190
+ def complete_resource resource_kind=nil
191
+ Conjur::Command.api.resources({kind: resource_kind})
192
+ .map do |r|
193
+ res = Resource.new r.attributes['id']
194
+ if resource_kind
195
+ res.name
196
+ else
197
+ res.to_s
198
+ end
199
+ end
200
+ end
201
+
202
+ def complete_role
203
+ Conjur::Command.api.current_role.all
204
+ .map { |r| Resource.new(r.roleid) }
205
+ .reject { |r| r.kind.start_with? '@' }
206
+ .map(&:to_s)
207
+ end
208
+
209
+ def complete_file word
210
+ # use Bash's file completion for compatibility
211
+ `bash -c "compgen -f #{word}"`.shellsplit
212
+ end
213
+
214
+ def complete_hostname
215
+ `bash -c "compgen -A hostname"`.shellsplit
216
+ end
217
+
218
+ def completions
219
+ prev = current_word(-1)
220
+ if current_word.start_with? '-'
221
+ complete_flags @commands.last
222
+ else
223
+ (subcommands @commands.last).keys.map(&:to_s) +
224
+ (complete_args @commands.last, prev, @arg_words.length)
225
+ end.flatten
226
+ .select do |candidate|
227
+ candidate.start_with? current_word.sub('\:',':') end
228
+ .map do |candidate|
229
+ # if the current word is colon separated, strip its complete tokens
230
+ # eg. for --as-role=user:ryanprior, we're actually only completing 'ryanprior'
231
+ # because bash treats 'user' as a separate word
232
+ non_escaped_colon_regex = /(?<!\\):/
233
+ num_tokens = current_word.split(non_escaped_colon_regex).length
234
+ if num_tokens > 1
235
+ candidate = candidate
236
+ .split(non_escaped_colon_regex)
237
+ .drop(num_tokens-1)
238
+ .join(':')
239
+ end
240
+ "#{candidate}#{' ' if not candidate.end_with? '='}" end
241
+ end
242
+ public :current_word, :completions
243
+ end
244
+
245
+ class Conjur::CLI::Complete::Resource
246
+ attr_reader :account, :kind, :name
247
+ attr_accessor :include_account
248
+ def initialize resource_string, include_account=false
249
+ @include_account = include_account
250
+ fields = resource_string.split ':'
251
+ raise ArgumentError.new "too many fields (#{resource_string})" if fields.length > 3
252
+ fields.unshift nil while fields.length < 3
253
+ @account, @kind, @name = fields
254
+ end
255
+
256
+ def to_ary
257
+ [(@account if @include_account), @kind, @name].reject { |a| a.nil? }
258
+ end
259
+
260
+ def to_s
261
+ to_ary.join ':'
262
+ end
263
+ 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.25.2"
22
+ VERSION = "4.26.0"
23
23
  ::Version=VERSION
24
24
  end
@@ -312,6 +312,25 @@ describe Conjur::Command::Audit, logged_in: true do
312
312
  expect { invoke }.to write(" created role super:user")
313
313
  end
314
314
  end
315
+
316
+ describe 'audit of ssh:sudo' do
317
+ let(:ssh_event) { default_audit_event.merge('kind' => 'audit', 'facility' => 'ssh', 'action' => 'sudo', 'command' => '/bin/ls', 'system_user' => 'test_user', 'target_user' => 'root') }
318
+ context 'when sudo successful' do
319
+ let(:test_event) { ssh_event.merge('allowed' => true) }
320
+ it 'prints <user> ran <command>' do
321
+ expect { invoke }.to write(" test_user ran '/bin/ls' as root")
322
+ end
323
+ end
324
+
325
+ context 'when sudo fails' do
326
+ let(:test_event) { ssh_event.merge('allowed' => false) }
327
+
328
+ it 'prints <user> attempted to run <command>' do
329
+ expect { invoke }.to write(" test_user attempted to run '/bin/ls' as root")
330
+ end
331
+ end
332
+ end
333
+
315
334
  end
316
335
  end
317
336
 
@@ -0,0 +1,265 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conjur::CLI::Complete do
4
+ def expects_completions_for string, point=nil
5
+ expect(described_class.new("conjur #{string}",point)
6
+ .completions
7
+ .map { |c| c.chomp ' ' })
8
+ end
9
+
10
+ describe 'conjur bash completion' do
11
+ describe 'for conjur subcommands beginning' do
12
+
13
+ before(:each) { expect(Conjur::Command).not_to receive :api }
14
+
15
+ context 'with "conjur gr"' do
16
+ it { expects_completions_for('gr').to include 'group' }
17
+ end
18
+
19
+ context 'with "conjur group"' do
20
+ it { expects_completions_for('group').to contain_exactly 'group' }
21
+ end
22
+
23
+ context 'with "conjur p"' do
24
+ it { expects_completions_for('p').to include 'plugin',
25
+ 'policy',
26
+ 'pubkeys' }
27
+ end
28
+
29
+ context 'with "conjur host l"' do
30
+ it { expects_completions_for('host l').to include 'layers',
31
+ 'list' }
32
+ end
33
+
34
+ context 'with "conjur policy"' do
35
+ it { expects_completions_for('policy ').to include 'load' }
36
+ end
37
+ end
38
+
39
+ describe 'for deprecated subcommands such as `conjur field`' do
40
+ it { expects_completions_for('fi').not_to include 'field' }
41
+ end
42
+
43
+ describe 'for command flags beginning' do
44
+
45
+ before(:each) { expect(Conjur::Command).not_to receive :api }
46
+
47
+ context 'conjur -' do
48
+ it { expects_completions_for('-').to include '--version' }
49
+ end
50
+
51
+ context 'conjur role memberships -' do
52
+ it { expects_completions_for('role memberships -')
53
+ .to include '-s', '--system'}
54
+ end
55
+
56
+ context 'conjur audit all -' do
57
+ it { expects_completions_for('audit all -s -')
58
+ .to include '-f', '--follow', '-l', '--limit=',
59
+ '-o', '--offset=', '-s', '--short' }
60
+ end
61
+
62
+ context 'conjur layer create --as-' do
63
+ it { expects_completions_for('layer create --as-')
64
+ .to include '--as-role=' }
65
+ end
66
+
67
+ context 'conjur group create --as-role' do
68
+ it { expects_completions_for('layer create --as-role')
69
+ .to contain_exactly '--as-role=' }
70
+ end
71
+ end
72
+
73
+ describe 'for arguments' do
74
+
75
+ let (:api) { double('api') }
76
+ before(:each) {
77
+ expect(Conjur::Command).to receive(:api).at_least(:once).and_return api
78
+ }
79
+
80
+ describe 'expecting a resource' do
81
+
82
+ let (:users) { ['Tweedledum', 'Tweedledee'] }
83
+ let (:groups) { ['sharks', 'jets'] }
84
+ let (:layers) { ['limbo', 'lust', 'gluttony', 'greed',
85
+ 'anger', 'heresy', 'violence', 'fraud',
86
+ 'treachery'] }
87
+ let (:variables) { ['id/superman', 'id/batman', 'id/spiderman'] }
88
+ let (:hosts) { ['skynet', 'hal9000', 'deep-thought'] }
89
+
90
+
91
+ def mock_resources
92
+ fake_results = yield.map { |result|
93
+ double('resource', :attributes => { 'id' => result })
94
+ }
95
+ expect(Conjur::Command.api).to receive(:resources)
96
+ .once.and_return fake_results
97
+ end
98
+
99
+ context 'with kind "user"' do
100
+ before(:each) { mock_resources { users.map { |user| "user:#{user}" }}}
101
+ it { expects_completions_for('user show ')
102
+ .to contain_exactly(*users) }
103
+ end
104
+
105
+ context 'with kind "group"' do
106
+ before(:each) {
107
+ mock_resources { groups.map { |group| "group:#{group}" }}
108
+ }
109
+ context 'for a command' do
110
+ it { expects_completions_for('group show ')
111
+ .to contain_exactly(*groups) }
112
+ end
113
+ context 'for a flag' do
114
+ it { expects_completions_for('group create --as-group=')
115
+ .to contain_exactly(*groups) }
116
+ end
117
+ end
118
+
119
+ context 'with kind "layer"' do
120
+ before(:each) {
121
+ mock_resources { layers.map { |layer| "layer:#{layer}" }}
122
+ }
123
+ it { expects_completions_for('layer show ')
124
+ .to contain_exactly(*layers) }
125
+ end
126
+
127
+ context 'with kind "variable"' do
128
+ before(:each) {
129
+ mock_resources { variables.map { |variable| "variable:#{variable}" }}
130
+ }
131
+ it { expects_completions_for('variable show ')
132
+ .to contain_exactly(*variables) }
133
+ end
134
+
135
+ context 'with kind "host"' do
136
+ before(:each) {
137
+ mock_resources { hosts.map { |host| "host:#{host}" }}
138
+ }
139
+ it { expects_completions_for('host show ')
140
+ .to contain_exactly(*hosts)
141
+ }
142
+ end
143
+
144
+ context 'without kind specified' do
145
+ let (:resources) { (users+groups+layers+variables+hosts)
146
+ .map { |res| "arbitrarykind:#{res}" }}
147
+ before(:each) { mock_resources { resources }}
148
+ it 'should show all resources with their reported kinds' do
149
+ expects_completions_for('resource show ')
150
+ .to contain_exactly(*resources)
151
+ end
152
+ end
153
+ end
154
+
155
+ describe 'expecting a role' do
156
+ let (:roles) { ['layer:population/tire',
157
+ 'host:bubs-4k',
158
+ 'user:strongbad',
159
+ 'user:strongsad',
160
+ 'user:strongmad']}
161
+ before(:each) {
162
+ role_doubles = roles.map { |role| double('role', :roleid => role) }
163
+ expect(api).to receive(:current_role).once
164
+ .and_return double('current_role', :all => role_doubles)
165
+ }
166
+ it { expects_completions_for('role memberships ')
167
+ .to contain_exactly(*roles) }
168
+ it 'completes colon separated values per-token' do
169
+ expects_completions_for('layer list --role=host:b')
170
+ .to contain_exactly 'bubs-4k'
171
+ end
172
+ it 'recognizes shell-escaped colons' do
173
+ expects_completions_for('role members layer\:pop')
174
+ .to contain_exactly 'layer:population/tire'
175
+ end
176
+ end
177
+ end
178
+
179
+ describe 'completes mid-line' do
180
+ it 'completes a subcommand not at the end of a line' do
181
+ expect(described_class.new('conjur gr create dwarves/7', 9).completions)
182
+ .to include 'group '
183
+ end
184
+ it 'tolerates garbage flags and arguments' do
185
+ expect(described_class.new('conjur omg --lol wat pu').completions)
186
+ .to include 'pubkeys '
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ describe Conjur::CLI::Complete::Resource do
193
+ describe 'splits resource ids' do
194
+ describe '#initialize(resource_string)' do
195
+ describe 'accepts long or brief ids' do
196
+ context 'gratuitous id (4+ tokens)' do
197
+ it 'raises an ArgumentError' do
198
+ expect {
199
+ described_class.new '1:2:3:4'
200
+ }.to raise_error ArgumentError
201
+ end
202
+ end
203
+ context 'long id (3 tokens)' do
204
+ it 'stores all 3 tokens' do
205
+ dummy_string = 'acct:kind:name'
206
+ dummy = described_class.new dummy_string
207
+ expect(dummy.account).to eq 'acct'
208
+ expect(dummy.kind).to eq 'kind'
209
+ expect(dummy.name).to eq 'name'
210
+ end
211
+ end
212
+ context 'brief id (2 tokens)' do
213
+ it 'stores tokens in kind and name' do
214
+ dummy = described_class.new 'kind:name'
215
+ expect(dummy.account).to eq nil
216
+ expect(dummy.kind).to eq 'kind'
217
+ expect(dummy.name).to eq 'name'
218
+ end
219
+ end
220
+ context 'tiny id (1 token)' do
221
+ it 'stores token in name' do
222
+ dummy = described_class.new 'name'
223
+ expect(dummy.account).to eq nil
224
+ expect(dummy.kind).to eq nil
225
+ expect(dummy.name).to eq 'name'
226
+ end
227
+ end
228
+ end
229
+ it 'hides the account by default' do
230
+ expect(described_class.new('a:b:c').include_account).to eq false
231
+ expect(described_class.new('a:b:c', true).include_account).to eq true
232
+ end
233
+ end
234
+
235
+ describe '#to_ary' do
236
+ context 'account not important' do
237
+ it 'hides account when converting to array' do
238
+ dummy = described_class.new 'a:b:c'
239
+ expect(dummy.to_ary).to eq ['b','c']
240
+ end
241
+ end
242
+ context 'account is important' do
243
+ it 'includes account when converting to array' do
244
+ dummy = described_class.new 'a:b:c', true
245
+ expect(dummy.to_ary).to eq ['a','b','c']
246
+ end
247
+ end
248
+ end
249
+
250
+ describe '#to_s' do
251
+ context 'account not important' do
252
+ it 'hides account when converting to string' do
253
+ dummy = described_class.new 'test:user:admin'
254
+ expect(dummy.to_s).to eq 'user:admin'
255
+ end
256
+ end
257
+ context 'account is important' do
258
+ it 'includes account when converting to string' do
259
+ dummy = described_class.new 'test:user:admin', true
260
+ expect(dummy.to_s).to eq 'test:user:admin'
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end