Class: Conjur::Command

Inherits:
Object
  • Object
show all
Extended by:
IdentifierManipulation
Defined in:
lib/conjur/command.rb,
lib/conjur/command/audit.rb

Defined Under Namespace

Classes: Assets, Audit, Authn, Bootstrap, Elevate, Env, Field, Groups, HostFactories, Hosts, Id, Init, LDAPSync, Layers, Plugin, Pubkeys, Resources, Roles, RubyDSL, Script, Secrets, Server, ShellInit, Users, Variables

Constant Summary collapse

@@api =
nil

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from IdentifierManipulation

conjur_account, full_resource_id, get_kind_and_id_from_args

Class Attribute Details

.prefix ⇒ Object

Returns the value of attribute prefix.



31
32
33
# File 'lib/conjur/command.rb', line 31

def prefix
  @prefix
end

Class Method Details

.acting_as_option(command) ⇒ Object



73
74
75
76
77
78
79
80
81
82
# File 'lib/conjur/command.rb', line 73

def acting_as_option command
  return if command.flags.member?(:"as-group") # avoid duplicate flags
  command.desc 'Perform all actions as the specified Group'
  command.arg_name 'GROUP'
  command.flag [:'as-group']

  command.desc 'Perform all actions as the specified Role'
  command.arg_name 'ROLE'
  command.flag [:'as-role']
end

.annotate_option(command) ⇒ Object



102
103
104
105
106
# File 'lib/conjur/command.rb', line 102

def annotate_option command
  command.arg_name 'annotate'
  command.desc 'Add variable annotations interactively'
  command.switch [:a, :annotate]
end

.api ⇒ Object



54
55
56
# File 'lib/conjur/command.rb', line 54

def api
  @@api ||= Conjur::Authn.connect
end

.api=(api) ⇒ Object



50
51
52
# File 'lib/conjur/command.rb', line 50

def api= api
  @@api = api
end

.assert_empty(args) ⇒ Object



46
47
48
# File 'lib/conjur/command.rb', line 46

def assert_empty(args)
  exit_now! "Received extra command arguments" unless args.empty?
end

.collection_option(command) ⇒ Object



84
85
86
87
88
# File 'lib/conjur/command.rb', line 84

def collection_option command
  command.desc 'An optional prefix for created roles and resources'
  command.arg_name 'collection'
  command.flag [:collection]
end

.command(name, *a, &block) ⇒ Object



37
38
39
40
# File 'lib/conjur/command.rb', line 37

def command name, *a, &block
  name = "#{prefix}:#{name}" if prefix
  Conjur::CLI.command(name, *a, &block)
end

.command_impl_for_list(global_options, options, args) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/conjur/command.rb', line 193

def command_impl_for_list(global_options, options, args)
  opts = process_command_options_for_search(options)
  resources = api.resources(opts)
  if resources.is_a?(Numeric)
    puts resources
  elsif options[:ids]
    puts JSON.pretty_generate(resources.map(&:resourceid))
  else
    resources = resources.map &:attributes
    unless options[:'raw-annotations']
      resources = resources.map do |r|
        r['annotations'] = (r['annotations'] || []).inject({}) do |hash, annot|
          hash[annot['name']] = annot['value']
          hash
        end
        r
      end
    end
    puts JSON.pretty_generate resources
  end
end

.command_option_as_role(c) ⇒ Object



151
152
153
154
155
156
# File 'lib/conjur/command.rb', line 151

def command_option_as_role c
  return if c.flags.member?(:role) # avoid duplicate flags
  c.desc "Role to act as. By default, the current logged-in role is used."
  c.arg_name 'ROLE'
  c.flag [:role]
end

.command_option_kind(c) ⇒ Object



146
147
148
149
# File 'lib/conjur/command.rb', line 146

def command_option_kind c
  c.desc "Filter by kind"
  c.flag [:k, :kind]
end

.command_options_for_list(c) ⇒ Object



172
173
174
175
176
177
178
179
180
181
# File 'lib/conjur/command.rb', line 172

def command_options_for_list(c)
  command_option_as_role c
  command_options_for_search c

  c.desc "Show only ids"
  c.switch [:i, :ids]

  c.desc "Show annotations in 'raw' format"
  c.switch [:r, :"raw-annotations"]
end

.command_options_for_search(c) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/conjur/command.rb', line 158

def command_options_for_search c
  c.desc "Full-text search on resource id and annotation values" 
  c.flag [:s, :search]
  
  c.desc "Offset to start from"
  c.flag [:o, :offset]
  
  c.desc "Maximum number of records to return"
  c.flag [:l, :limit]

  c.desc "Show the number of results, rather than printing them"
  c.switch [:count]
end

.context_option(command) ⇒ Object



90
91
92
93
94
# File 'lib/conjur/command.rb', line 90

def context_option command
  command.desc "Load context from this config file, and save it when finished. The file permissions will be 0600 by default."
  command.arg_name "FILE"
  command.flag [:c, :context]
end

.current_role ⇒ Object



512
513
514
515
516
517
518
519
# File 'lib/conjur/command.rb', line 512

def current_role
  kind, id = api.username.split('/', 2)
  if id.nil?
    id = kind
    kind = 'user'
  end
  api.role([ kind, id ].join(":"))
end

.current_user ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/conjur/command.rb', line 58

def current_user
  username = api.username
  kind, id = username.split('/')
  unless kind && id
    id = kind
    kind = 'user'
  end
  api.send(kind, username)
end

.destination_role(options) ⇒ Object



235
236
237
238
239
240
241
242
# File 'lib/conjur/command.rb', line 235

def destination_role options
  destination = options[:"destination-role"]
  if destination
    api.role(destination)
  else
    api.user('attic')
  end
end

.display(obj, options = {}) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/conjur/command.rb', line 373

def display(obj, options = {})
  str = if obj.is_a?(Numeric)
    obj.to_s
  elsif obj.respond_to?(:attributes)
    JSON.pretty_generate obj.attributes
  elsif obj.respond_to?(:id)
    obj.id
  else
    begin
      JSON.pretty_generate(obj)
    rescue JSON::GeneratorError
      obj.to_json
    end
  end
  puts str
end

.display_members(members, member_field, options) ⇒ Numeric, Hash

Displays members, which may be either:

  • A numeric count
  • A list of role ids
  • A list of RoleGrant

options may include:

  • V Show full RoleGrant info
  • system show system (internal) roles

Returns:

  • (Numeric, Hash) —

    Convert the input to a number or Hash.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/conjur/command.rb', line 337

def display_members(members, member_field, options)
  # Get this case out of the way
  return display(members) if members.is_a?(Numeric)

  result, roleid_function = if members.blank?
    [ [], nil ]
  elsif members.first.is_a?(Conjur::RoleGrant)
    if options[:V]
      items = members.collect do |member|
        {
          member: member.member.roleid,
          grantor: member.grantor.roleid,
          admin_option: member.admin_option
        }.tap do |obj|
          obj[:role] = member.role.roleid if member.role
        end
      end
      [
        items, lambda {|member| member[member_field] }
      ]
    else
      [ members.map{|m| m.send(member_field) }.map(&:roleid), lambda{|r| r} ]
    end
  else
    [ members.map(&:roleid), lambda{|r| r} ]
  end

  unless options[:system]
    result.reject! do |member|
      roleid_function.call(member) =~ /^.+?:@/
    end
  end

  display result
end

.elevated? ⇒ Boolean

Returns:

  • (Boolean)


244
245
246
# File 'lib/conjur/command.rb', line 244

def elevated?
  api.privilege == 'elevate' && api.global_privilege_permitted?('elevate')
end

.give_away_resource(obj, options) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/conjur/command.rb', line 311

def give_away_resource obj, options
  destination = options[:"destination-role"]
  destination_role = if destination
    api.role(destination)
  else
    api.user('attic')
  end

  exit_now! "Role #{destination_role.roleid} doesn't exist" unless destination_role.exists?
  
  puts "Giving ownership to '#{destination_role.roleid}'"
  obj.resource.give_to destination_role
end

.has_admin?(role, other_role) ⇒ Boolean

Returns:

  • (Boolean)


521
522
523
524
525
526
527
# File 'lib/conjur/command.rb', line 521

def has_admin?(role, other_role)
  return true if role.roleid == other_role.roleid
  memberships = role.memberships.map(&:roleid)
  other_role.members.any? { |m| memberships.member?(m.member.roleid) && m.admin_option }
rescue RestClient::Forbidden
  false
end

.hide_docs(command) ⇒ Object

Prevent a deprecated command from being displayed in the help output



69
70
71
# File 'lib/conjur/command.rb', line 69

def hide_docs(command)
  def command.nodoc; true end
end

.highline ⇒ Object



128
129
130
131
# File 'lib/conjur/command.rb', line 128

def highline
  require 'highline'
  @highline ||= HighLine.new($stdin,$stderr)
end

.integer?(v) ⇒ Boolean

Returns:

  • (Boolean)


402
403
404
# File 'lib/conjur/command.rb', line 402

def integer? v
  Integer(v, 10) rescue false
end

.interactive_option(command) ⇒ Object



96
97
98
99
100
# File 'lib/conjur/command.rb', line 96

def interactive_option command
  command.arg_name 'interactive'
  command.desc 'Create variable interactively'
  command.switch [:i, :'interactive']
end

.method_missing(*a, &b) ⇒ Object



33
34
35
# File 'lib/conjur/command.rb', line 33

def method_missing *a, &b
  Conjur::CLI.send *a, &b
end

.min_version(command, version) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'lib/conjur/command.rb', line 108

def min_version command, version
  version = Semantic::Version.new version
  if version.pre == nil
    # Version check doesn't work correctly if one version has
    # the "-###" suffix and the other does not. Versions
    # returned by the server have the suffix.
    version.pre = "0"
  end
  command.instance_variable_set(:@conjur_min_version, version)
end

.notify_deprecated ⇒ Object



529
530
531
# File 'lib/conjur/command.rb', line 529

def notify_deprecated
  STDERR.puts 'WARNING! This command is deprecated and will be removed. Use policy instead.'
end

.process_command_options_for_search(options) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/conjur/command.rb', line 183

def process_command_options_for_search options
  opts = options.slice(:search, :count, :limit, :offset, :kind)
  opts[:acting_as] = options[:role] if options[:role]
  opts[:search] = opts[:search].gsub('-',' ') if opts[:search]
  if opts[:kind] && opts[:kind].index(',')
    opts[:kind] = opts[:kind].split(',')
  end
  opts
end

.prompt_for_annotations ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/conjur/command.rb', line 119

def prompt_for_annotations
  highline.say('Add annotations (a name and value for each one):')
  {}.tap do |annotations|
    until (name = highline.ask('  annotation name (press enter to quit annotations): ')).empty?
      annotations[name] = read_till_eof('  annotation value (^D on its own line to finish):')
    end
  end
end

.prompt_for_group(options = {}) ⇒ Object



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/conjur/command.rb', line 461

def prompt_for_group options = {}
  options[:hint] ||= "press enter to own the record yourself"
  group_ids = api.groups.map(&:id)
  
  highline.ask("Enter the group which will own the record (#{options[:hint]}): ", [ "" ] + group_ids) do |q|
    require 'readline'
    Readline.completion_append_character = ""
    Readline.completer_word_break_characters = ""
    
    q.readline = true
    q.validate = lambda{|id|
      @group = nil
      id.empty? || (@group = api.group(id)).exists?
    }
    q.responses[:not_valid] = "Group '<%= @answer %>' doesn't exist, or you don't have permission to use it"
  end
  @group ? @group.roleid : nil
end

.prompt_for_id(kind, label = 'id') ⇒ Object



406
407
408
409
410
411
412
413
414
415
416
# File 'lib/conjur/command.rb', line 406

def prompt_for_id kind, label = 'id'
  highline.ask("Enter the #{label}: ") do |q|
    q.readline = true
    q.validate = lambda{|id|
      !id.blank? && !api.send(kind, id).exists?
    }
    q.responses[:not_valid] = "<% if @answer.blank? %>"\
        "#{label} cannot be blank<% else %>"\
        "A #{kind} called '<%= @answer %>' already exists<% end %>"
  end
end

.prompt_for_idnumber(label) ⇒ Object



480
481
482
483
484
485
486
487
488
# File 'lib/conjur/command.rb', line 480

def prompt_for_idnumber label
  result = highline.ask("Enter a #{label}: ") do |q|
    q.validate = lambda{|id|
      id.blank? || integer?(id)
    }
    q.responses[:not_valid] = "The #{label} must be an integer"
  end
  result.blank? ? nil : result.to_i
end

.prompt_for_password ⇒ Object



490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/conjur/command.rb', line 490

def prompt_for_password
  require 'highline'
  # use stderr to allow output redirection, e.g.
  # conjur user:create -p username > user.json
  hl = HighLine.new($stdin, $stderr)
    
  password = hl.ask("Enter the password (it will not be echoed): "){ |q| q.echo = false }
  if password.blank?
    if hl.agree "No password (y/n)?"
      return nil
    else
      return prompt_for_password
    end
  end

  confirmation = hl.ask("Confirm the password: "){ |q| q.echo = false }
  
  raise "Password does not match confirmation" unless password == confirmation
  
  password
end

.prompt_for_public_key ⇒ Object



418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/conjur/command.rb', line 418

def prompt_for_public_key
  public_key = highline.ask("Enter the public key (press enter to skip): ") do |q|
    q.validate = lambda{|key|
      if key.blank?
        true
      else
        validate_public_key key
      end
    }
    q.responses[:not_valid] = "Public key format is invalid; please try again"
  end
  public_key.blank? ? nil : public_key.strip
end

.prompt_to_confirm(kind, properties) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
# File 'lib/conjur/command.rb', line 390

def prompt_to_confirm kind, properties
  puts
  puts "A new #{kind} will be created with the following properties:"
  puts
  properties.select{|k,v| !v.blank?}.each do |k,v|
    printf "%-10s: %s\n", k, v
  end
  puts
  
  exit(0) unless %w(yes y).member?(highline.ask("Proceed? (yes/no): ").strip.downcase)
end

.read_till_eof(prompt = nil) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/conjur/command.rb', line 133

def read_till_eof(prompt = nil)
  highline.say(prompt) if prompt
  [].tap do |lines|
    loop do
      begin
        lines << highline.ask('')
      rescue EOFError
        break
      end
    end
  end.join("\n")
end

.require_arg(args, name) ⇒ Object



42
43
44
# File 'lib/conjur/command.rb', line 42

def require_arg(args, name)
  args.shift or raise "Missing parameter: #{name}"
end

.retire_internal_role(roleObj) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/conjur/command.rb', line 295

def retire_internal_role roleObj
  members = roleObj.members
  # Move the invoking role to the end of the roles list, so that it doesn't
  # lose its permissions in the middle of this operation.
  self_member = members.select{|m| m.member.roleid == current_user.role.roleid}
  self_member.each do |m|
    members.delete m
  end
  members.concat self_member if self_member
  members.each do |r|
    member = api.role(r.member)
    puts "Revoking from role #{member.roleid}"
    roleObj.revoke_from member
  end
end

.retire_options(command) ⇒ Object



224
225
226
227
228
229
230
231
232
233
# File 'lib/conjur/command.rb', line 224

def retire_options command
  command.arg_name 'role'
  command.desc "Specify a role to give the retired record to (default: the 'attic' user)"
  command.long_desc %Q(When retired, all a record's roles and permissions are revoked.
  
As a final step, the record is 'given' (e.g. 'conjur resource give') to a destination role.
The default role to receive the record is the user 'attic'. This option can be used to specify
an alternative destination role.)
  command.flag [:d, :"destination-role"]
end

.retire_resource(obj) ⇒ Object



268
269
270
271
272
273
274
275
276
# File 'lib/conjur/command.rb', line 268

def retire_resource obj
  obj.resource.attributes['permissions'].each do |p|
    role = api.role(p['role'])
    privilege = p['privilege']
    next if obj.respond_to?(:roleid) && role.roleid == obj.roleid && privilege == 'read'
    puts "Denying #{privilege} privilege to #{role.roleid}"
    obj.resource.deny(privilege, role)
  end
end

.retire_role(obj) ⇒ Object



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/conjur/command.rb', line 278

def retire_role obj
  members = obj.role.members
  # Move the invoking role to the end of the roles list, so that it doesn't
  # lose its permissions in the middle of this operation.
  # I'm sure there's a cleaner way to do this.
  self_member = members.select{|m| m.member.roleid == current_user.role.roleid}
  self_member.each do |m|
    members.delete m
  end
  members.concat self_member if self_member
  members.each do |r|
    member = api.role(r.member)
    puts "Revoking from role #{member.roleid}"
    obj.role.revoke_from member
  end
end

.validate_privileges(message, &block) ⇒ Object



215
216
217
218
219
220
221
222
# File 'lib/conjur/command.rb', line 215

def validate_privileges message, &block
  valid = begin
    yield
  rescue RestClient::Forbidden
    false
  end
  exit_now! message unless valid
end

.validate_public_key(key) ⇒ Object



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/conjur/command.rb', line 433

def validate_public_key key
  if system('which ssh-keygen 2>&1 > /dev/null')
    Conjur.log.debug "Using ssh-keygen to verify the public key\n" if Conjur.log
    require 'tempfile'
    tempfile = Tempfile.new 'public_key'
    tempfile.write(key)
    tempfile.close
    `ssh-keygen -l -f #{tempfile.path}`
    $? == 0
  else
    Conjur.log.debug "ssh-keygen is not available; falling back to simple string testing\n" if Conjur.log
    # Should be a line with at least 2 components,
    # first one being the algo id and second a base64 string.
    # In principle this means:
    #   Base64.strict_decode64 key.strip[/\Assh-\w+ (\S+).*/, 1]

    # Since the pubkeys service is more strict: needs a name and
    # rejects ones with a space, instead reproduce its algorithm here.
    begin
      components = key.strip.split ' '
      Base64.strict_decode64 components[1]
      components.length == 3
    rescue NoMethodError, ArgumentError
      false
    end
  end
end

.validate_retire_privileges(record, options) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/conjur/command.rb', line 248

def validate_retire_privileges record, options
  return true if elevated?
  
  if record.respond_to?(:role)
    memberships = current_user.role.memberships.map(&:roleid)
    validate_privileges "You can't administer this record" do
      # The current user has a role which is admin of the record's role
      record.role.members.find{|m| memberships.member?(m.member.roleid) && m.admin_option}
    end
  end
  
  validate_privileges "You don't own the record" do
    # The current user has the role which owns the record's resource
    current_user.role.member_of?(record.resource.ownerid)
  end
  
  role = destination_role(options)
  exit_now! "Destination role '#{role.roleid}' doesn't exist" unless role.exists?
end