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

.prefixObject

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

.apiObject



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



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/conjur/command.rb', line 168

def command_impl_for_list(global_options, options, args)
  opts = options.slice(:search, :limit, :options, :kind) 
  opts[:acting_as] = options[:role] if options[:role]
  opts[:search]=opts[:search].gsub('-',' ') if opts[:search]
  resources = api.resources(opts)
  if 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_options_for_list(c) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/conjur/command.rb', line 146

def command_options_for_list(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]
    
  c.desc "Full-text search on resource id and annotation values" 
  c.flag [:s, :search]
  
  c.desc "Maximum number of records to return"
  c.flag [:l, :limit]
  
  c.desc "Offset to start from"
  c.flag [:o, :offset]
  
  c.desc "Show only ids"
  c.switch [:i, :ids]
  
  c.desc "Show annotations in 'raw' format"
  c.switch [:r, :"raw-annotations"]
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_roleObject



452
453
454
455
456
457
458
459
# File 'lib/conjur/command.rb', line 452

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

.current_userObject



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



210
211
212
213
214
215
216
217
# File 'lib/conjur/command.rb', line 210

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

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



315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/conjur/command.rb', line 315

def display(obj, options = {})
  str = if 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, options) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/conjur/command.rb', line 300

def display_members(members, options)
  result = if options[:V]
    members.collect {|member|
      {
        member: member.member.roleid,
        grantor: member.grantor.roleid,
        admin_option: member.admin_option
      }
    }
  else
    members.map(&:member).map(&:roleid)
  end
  display result
end

.elevated?Boolean

Returns:

  • (Boolean)


219
220
221
# File 'lib/conjur/command.rb', line 219

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

.give_away_resource(obj, options) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/conjur/command.rb', line 286

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)


461
462
463
464
465
466
467
# File 'lib/conjur/command.rb', line 461

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

.highlineObject



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)


342
343
344
# File 'lib/conjur/command.rb', line 342

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_deprecatedObject



469
470
471
# File 'lib/conjur/command.rb', line 469

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

.prompt_for_annotationsObject



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



401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/conjur/command.rb', line 401

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



346
347
348
349
350
351
352
353
354
355
356
# File 'lib/conjur/command.rb', line 346

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



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

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_passwordObject



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/conjur/command.rb', line 430

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_keyObject



358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/conjur/command.rb', line 358

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



330
331
332
333
334
335
336
337
338
339
340
# File 'lib/conjur/command.rb', line 330

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



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/conjur/command.rb', line 270

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



199
200
201
202
203
204
205
206
207
208
# File 'lib/conjur/command.rb', line 199

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



243
244
245
246
247
248
249
250
251
# File 'lib/conjur/command.rb', line 243

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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/conjur/command.rb', line 253

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



190
191
192
193
194
195
196
197
# File 'lib/conjur/command.rb', line 190

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

.validate_public_key(key) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/conjur/command.rb', line 373

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



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/conjur/command.rb', line 223

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