conjur-cli 4.24.0 → 4.25.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 +4 -4
- data/CHANGELOG.md +10 -1
- data/PUBLISH.md +26 -0
- data/lib/conjur/authn.rb +2 -0
- data/lib/conjur/cli.rb +2 -2
- data/lib/conjur/command.rb +231 -2
- data/lib/conjur/command/bootstrap.rb +74 -8
- data/lib/conjur/command/groups.rb +36 -10
- data/lib/conjur/command/hosts.rb +5 -3
- data/lib/conjur/command/pubkeys.rb +25 -3
- data/lib/conjur/command/resources.rb +14 -4
- data/lib/conjur/command/rspec/mock_services.rb +3 -0
- data/lib/conjur/command/users.rb +53 -15
- data/lib/conjur/command/variables.rb +47 -77
- data/lib/conjur/version.rb +1 -1
- data/spec/command/pubkeys_spec.rb +2 -0
- data/spec/command/resources_spec.rb +10 -0
- data/spec/command/variables_spec.rb +61 -26
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9327368d238b90717af3151f2fc1a2091ff4f051
|
|
4
|
+
data.tar.gz: a90f6d8e898919557b9b20f54435a5b07d508f61
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3b6e29c9c849478d5a67e50d0d59b6e5973dced0d58010d00fbdd1c2dc5287911a9f308c5b9ba9bc6320ea6fedfcb9e098b53582227b1231e95b3693d2a6bb1
|
|
7
|
+
data.tar.gz: a7a9a8fb315d6fd1dd089e6e0e3cd2330ea98f8ccb37f555eea056d09a5d258660b9b6f793f475951390d2de0c597e01ec04f339dd2e4a670d14c78e93b526be
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 4.25.0
|
|
2
|
+
|
|
3
|
+
* A record can be retired to a specific role, in addition to the default behavior of retiring to the `attic` user.
|
|
4
|
+
* Variable can be created with the id only, without becoming interactive
|
|
5
|
+
* Run `conjur variable create -i -a` to create interactively with annotations
|
|
6
|
+
* Interactive annotation can be performed on bare resources with `conjur resource annotate -i`.
|
|
7
|
+
* Don't require 'admin' user to bootstrap, prompt to create a new security admin during bootstrap
|
|
8
|
+
* Check if user privileges are sufficient before running `retire`
|
|
9
|
+
* Don't revoke a user's access to a record in the middle of retire, because doing so leads to 403 errors later on.
|
|
10
|
+
* Interactive mode of user, group and pubkey creation
|
|
2
11
|
|
|
3
12
|
# 4.24.0
|
|
4
13
|
|
data/PUBLISH.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Publishing the CLI
|
|
2
|
+
|
|
3
|
+
We distribute the Conjur CLI as a package for Ubuntu, Centos, OSX and also as a rubygem.
|
|
4
|
+
|
|
5
|
+
Steps to publish a new version of the CLI:
|
|
6
|
+
|
|
7
|
+
1. Update `VERSION` in [lib/conjur/version.rb](lib/conjur/version.rb)
|
|
8
|
+
2. Update the [CHANGELOG.md](CHANGELOG.md) with any changes for this version
|
|
9
|
+
3. Commit these changes with the message `"v#{VERSION}"`, where `VERSION` = the new version
|
|
10
|
+
4. Go to the specific build page for the commit [in Jenkins](https://jenkins.conjur.net/job/cli-ruby/)
|
|
11
|
+
5. In the left sidebar, open `Promotion Status`
|
|
12
|
+
6. Click `Approve` for the "rubygems" promotion and wait for it to finish
|
|
13
|
+
7. Click `Approve` for the "packages" promotion, this will kick off the [omnibus-conjur](https://jenkins.conjur.net/job/omnibus-conjur/) build flow [1](#ref1).
|
|
14
|
+
8. Download the [deb](https://jenkins.conjur.net/job/omnibus-conjur-ubuntu/), [rpm](https://jenkins.conjur.net/job/omnibus-conjur-centos/) and [pkg](https://jenkins.conjur.net/job/omnibus-conjur-osx/) packages from their build pages in Jenkins.
|
|
15
|
+
9. Move the downloaded files to the `pkg` folder in your local [omnibus-conjur](https://github.com/conjurinc/omnibus-conjur) project.
|
|
16
|
+
10. In the `omnibus-conjur` project, upload each file to S3 with `./publish pkg/<filename>`.
|
|
17
|
+
11. Update the links on the [CLI page](https://github.com/conjurinc/developer-www/blob/master/app/views/pages/cli/index.html.haml) for the devsite.
|
|
18
|
+
12. Promote the devsite to production [in Jenkins](https://jenkins.conjur.net/job/developer-www/) [2](#ref2).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<a id="ref1">1</a>:
|
|
23
|
+
The packages promotion depends on the new gem version being published.
|
|
24
|
+
|
|
25
|
+
<a id="ref2">2</a>
|
|
26
|
+
After deploy it will take a few minutes for the cache to update.
|
data/lib/conjur/authn.rb
CHANGED
data/lib/conjur/cli.rb
CHANGED
|
@@ -113,12 +113,12 @@ module Conjur
|
|
|
113
113
|
group = Conjur::Command.api.group(as_group)
|
|
114
114
|
role = Conjur::Command.api.role(group.roleid)
|
|
115
115
|
exit_now!("Group '#{as_group}' doesn't exist, or you don't have permission to use it") unless role.exists?
|
|
116
|
-
options[:
|
|
116
|
+
options[:ownerid] = group.roleid
|
|
117
117
|
end
|
|
118
118
|
if as_role = options.delete(:"as-role")
|
|
119
119
|
role = Conjur::Command.api.role(as_role)
|
|
120
120
|
exit_now!("Role '#{as_role}' does not exist, or you don't have permission to use it") unless role.exists?
|
|
121
|
-
options[:
|
|
121
|
+
options[:ownerid] = role.roleid
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
true
|
data/lib/conjur/command.rb
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
19
19
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
20
|
#
|
|
21
|
+
require 'base64'
|
|
22
|
+
|
|
21
23
|
module Conjur
|
|
22
24
|
class Command
|
|
23
25
|
extend Conjur::IdentifierManipulation
|
|
@@ -42,13 +44,23 @@ module Conjur
|
|
|
42
44
|
def api
|
|
43
45
|
@@api ||= Conjur::Authn.connect
|
|
44
46
|
end
|
|
47
|
+
|
|
48
|
+
def current_user
|
|
49
|
+
username = api.username
|
|
50
|
+
kind, id = username.split('/')
|
|
51
|
+
unless kind && id
|
|
52
|
+
id = kind
|
|
53
|
+
kind = 'user'
|
|
54
|
+
end
|
|
55
|
+
api.send(kind, username)
|
|
56
|
+
end
|
|
45
57
|
|
|
46
58
|
# Prevent a deprecated command from being displayed in the help output
|
|
47
59
|
def hide_docs(command)
|
|
48
60
|
def command.nodoc; true end
|
|
49
61
|
end
|
|
50
62
|
|
|
51
|
-
def acting_as_option
|
|
63
|
+
def acting_as_option command
|
|
52
64
|
return if command.flags.member?(:"as-group") # avoid duplicate flags
|
|
53
65
|
command.arg_name 'Perform all actions as the specified Group'
|
|
54
66
|
command.flag [:"as-group"]
|
|
@@ -57,6 +69,45 @@ module Conjur
|
|
|
57
69
|
command.flag [:"as-role"]
|
|
58
70
|
end
|
|
59
71
|
|
|
72
|
+
def interactive_option command
|
|
73
|
+
command.arg_name 'interactive'
|
|
74
|
+
command.desc 'Create variable interactively'
|
|
75
|
+
command.switch [:i, :'interactive']
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def annotate_option command
|
|
79
|
+
command.arg_name 'annotate'
|
|
80
|
+
command.desc 'Add variable annotations interactively'
|
|
81
|
+
command.switch [:a, :annotate]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def prompt_for_annotations
|
|
85
|
+
highline.say('Add annotations (a name and value for each one):')
|
|
86
|
+
{}.tap do |annotations|
|
|
87
|
+
until (name = highline.ask(' annotation name (press enter to quit annotations): ')).empty?
|
|
88
|
+
annotations[name] = read_till_eof(' annotation value (^D on its own line to finish):')
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def highline
|
|
94
|
+
require 'highline'
|
|
95
|
+
@highline ||= HighLine.new($stdin,$stderr)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def read_till_eof(prompt = nil)
|
|
99
|
+
highline.say(prompt) if prompt
|
|
100
|
+
[].tap do |lines|
|
|
101
|
+
loop do
|
|
102
|
+
begin
|
|
103
|
+
lines << highline.ask('')
|
|
104
|
+
rescue EOFError
|
|
105
|
+
break
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end.join("\n")
|
|
109
|
+
end
|
|
110
|
+
|
|
60
111
|
def command_options_for_list(c)
|
|
61
112
|
return if c.flags.member?(:role) # avoid duplicate flags
|
|
62
113
|
c.desc "Role to act as. By default, the current logged-in role is used."
|
|
@@ -100,6 +151,53 @@ module Conjur
|
|
|
100
151
|
end
|
|
101
152
|
end
|
|
102
153
|
|
|
154
|
+
def validate_privileges message, &block
|
|
155
|
+
valid = begin
|
|
156
|
+
yield
|
|
157
|
+
rescue RestClient::Forbidden
|
|
158
|
+
false
|
|
159
|
+
end
|
|
160
|
+
exit_now! message unless valid
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def retire_options command
|
|
164
|
+
command.arg_name 'role'
|
|
165
|
+
command.desc "Specify a role to give the retired record to (default: the 'attic' user)"
|
|
166
|
+
command.long_desc %Q(When retired, all a record's roles and permissions are revoked.
|
|
167
|
+
|
|
168
|
+
As a final step, the record is 'given' (e.g. 'conjur resource give') to a destination role.
|
|
169
|
+
The default role to receive the record is the user 'attic'. This option can be used to specify
|
|
170
|
+
an alternative destination role.)
|
|
171
|
+
command.flag [:d, :"destination-role"]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def destination_role options
|
|
175
|
+
destination = options[:"destination-role"]
|
|
176
|
+
if destination
|
|
177
|
+
api.role(destination)
|
|
178
|
+
else
|
|
179
|
+
api.user('attic')
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def validate_retire_privileges record, options
|
|
184
|
+
if record.respond_to?(:role)
|
|
185
|
+
memberships = current_user.role.memberships.map(&:roleid)
|
|
186
|
+
validate_privileges "You can't administer this record" do
|
|
187
|
+
# The current user has a role which is admin of the record's role
|
|
188
|
+
record.role.members.find{|m| memberships.member?(m.member.roleid) && m.admin_option}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
validate_privileges "You don't own the record" do
|
|
193
|
+
# The current user has the role which owns the record's resource
|
|
194
|
+
current_user.role.member_of?(record.resource.ownerid)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
role = destination_role(options)
|
|
198
|
+
exit_now! "Destination role '#{role.roleid}' doesn't exist" unless role.exists?
|
|
199
|
+
end
|
|
200
|
+
|
|
103
201
|
def retire_resource obj
|
|
104
202
|
obj.resource.attributes['permissions'].each do |p|
|
|
105
203
|
role = api.role(p['role'])
|
|
@@ -111,13 +209,36 @@ module Conjur
|
|
|
111
209
|
end
|
|
112
210
|
|
|
113
211
|
def retire_role obj
|
|
114
|
-
obj.role.members
|
|
212
|
+
members = obj.role.members
|
|
213
|
+
# Move the invoking role to the end of the roles list, so that it doesn't
|
|
214
|
+
# lose its permissions in the middle of this operation.
|
|
215
|
+
# I'm sure there's a cleaner way to do this.
|
|
216
|
+
self_member = members.select{|m| m.member.roleid == current_user.role.roleid}
|
|
217
|
+
self_member.each do |m|
|
|
218
|
+
members.delete m
|
|
219
|
+
end
|
|
220
|
+
members.concat self_member if self_member
|
|
221
|
+
members.each do |r|
|
|
115
222
|
member = api.role(r.member)
|
|
116
223
|
puts "Revoking from role #{member.roleid}"
|
|
117
224
|
obj.role.revoke_from member
|
|
118
225
|
end
|
|
119
226
|
end
|
|
120
227
|
|
|
228
|
+
def give_away_resource obj, options
|
|
229
|
+
destination = options[:"destination-role"]
|
|
230
|
+
destination_role = if destination
|
|
231
|
+
api.role(destination)
|
|
232
|
+
else
|
|
233
|
+
api.user('attic')
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
exit_now! "Role #{destination_role.roleid} doesn't exist" unless destination_role.exists?
|
|
237
|
+
|
|
238
|
+
puts "Giving ownership to '#{destination_role.roleid}'"
|
|
239
|
+
obj.resource.give_to destination_role
|
|
240
|
+
end
|
|
241
|
+
|
|
121
242
|
def display_members(members, options)
|
|
122
243
|
result = if options[:V]
|
|
123
244
|
members.collect {|member|
|
|
@@ -148,6 +269,106 @@ module Conjur
|
|
|
148
269
|
puts str
|
|
149
270
|
end
|
|
150
271
|
|
|
272
|
+
def prompt_to_confirm kind, properties
|
|
273
|
+
puts
|
|
274
|
+
puts "A new #{kind} will be created with the following properties:"
|
|
275
|
+
puts
|
|
276
|
+
properties.select{|k,v| !v.blank?}.each do |k,v|
|
|
277
|
+
printf "%-10s: %s\n", k, v
|
|
278
|
+
end
|
|
279
|
+
puts
|
|
280
|
+
|
|
281
|
+
exit(0) unless %w(yes y).member?(highline.ask("Proceed? (yes/no): ").strip.downcase)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def integer? v
|
|
285
|
+
Integer(v, 10) rescue false
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def prompt_for_id kind, label = 'id'
|
|
289
|
+
highline.ask("Enter the #{label}: ") do |q|
|
|
290
|
+
q.readline = true
|
|
291
|
+
q.validate = lambda{|id|
|
|
292
|
+
!id.blank? && !api.send(kind, id).exists?
|
|
293
|
+
}
|
|
294
|
+
q.responses[:not_valid] = "<% if @answer.blank? %>"\
|
|
295
|
+
"#{label} cannot be blank<% else %>"\
|
|
296
|
+
"A #{kind} called '<%= @answer %>' already exists<% end %>"
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def prompt_for_public_key
|
|
301
|
+
public_key = highline.ask("Enter the public key (press enter to skip): ") do |q|
|
|
302
|
+
q.validate = lambda{|key|
|
|
303
|
+
if key.blank?
|
|
304
|
+
true
|
|
305
|
+
else
|
|
306
|
+
validate_public_key key
|
|
307
|
+
end
|
|
308
|
+
}
|
|
309
|
+
q.responses[:not_valid] = "Public key format is invalid; please try again"
|
|
310
|
+
end
|
|
311
|
+
public_key.blank? ? nil : public_key.strip
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# http://serverfault.com/questions/453296/how-do-i-validate-a-rsa-ssh-public-key-file-id-rsa-pub
|
|
315
|
+
def validate_public_key key
|
|
316
|
+
if system('which ssh-keygen 2>&1 > /dev/null')
|
|
317
|
+
Conjur.log.debug "Using ssh-keygen to verify the public key\n" if Conjur.log
|
|
318
|
+
require 'tempfile'
|
|
319
|
+
tempfile = Tempfile.new 'public_key'
|
|
320
|
+
tempfile.write(key)
|
|
321
|
+
tempfile.close
|
|
322
|
+
`ssh-keygen -l -f #{tempfile.path}`
|
|
323
|
+
$? == 0
|
|
324
|
+
else
|
|
325
|
+
Conjur.log.debug "ssh-keygen is not available; falling back to simple string testing\n" if Conjur.log
|
|
326
|
+
# Should be a line with at least 2 components,
|
|
327
|
+
# first one being the algo id and second a base64 string.
|
|
328
|
+
# In principle this means:
|
|
329
|
+
# Base64.strict_decode64 key.strip[/\Assh-\w+ (\S+).*/, 1]
|
|
330
|
+
|
|
331
|
+
# Since the pubkeys service is more strict: needs a name and
|
|
332
|
+
# rejects ones with a space, instead reproduce its algorithm here.
|
|
333
|
+
begin
|
|
334
|
+
components = key.strip.split ' '
|
|
335
|
+
Base64.strict_decode64 components[1]
|
|
336
|
+
components.length == 3
|
|
337
|
+
rescue NoMethodError, ArgumentError
|
|
338
|
+
false
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def prompt_for_group options = {}
|
|
344
|
+
options[:hint] ||= "press enter to own the record yourself"
|
|
345
|
+
group_ids = api.groups.map(&:id)
|
|
346
|
+
|
|
347
|
+
highline.ask("Enter the group which will own the record (#{options[:hint]}): ", [ "" ] + group_ids) do |q|
|
|
348
|
+
require 'readline'
|
|
349
|
+
Readline.completion_append_character = ""
|
|
350
|
+
Readline.completer_word_break_characters = ""
|
|
351
|
+
|
|
352
|
+
q.readline = true
|
|
353
|
+
q.validate = lambda{|id|
|
|
354
|
+
@group = nil
|
|
355
|
+
id.empty? || (@group = api.group(id)).exists?
|
|
356
|
+
}
|
|
357
|
+
q.responses[:not_valid] = "Group '<%= @answer %>' doesn't exist, or you don't have permission to use it"
|
|
358
|
+
end
|
|
359
|
+
@group ? @group.roleid : nil
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def prompt_for_idnumber label
|
|
363
|
+
result = highline.ask("Enter a #{label}: ") do |q|
|
|
364
|
+
q.validate = lambda{|id|
|
|
365
|
+
id.blank? || integer?(id)
|
|
366
|
+
}
|
|
367
|
+
q.responses[:not_valid] = "The #{label} must be an integer"
|
|
368
|
+
end
|
|
369
|
+
result.blank? ? nil : result.to_i
|
|
370
|
+
end
|
|
371
|
+
|
|
151
372
|
def prompt_for_password
|
|
152
373
|
require 'highline'
|
|
153
374
|
# use stderr to allow output redirection, e.g.
|
|
@@ -155,6 +376,14 @@ module Conjur
|
|
|
155
376
|
hl = HighLine.new($stdin, $stderr)
|
|
156
377
|
|
|
157
378
|
password = hl.ask("Enter the password (it will not be echoed): "){ |q| q.echo = false }
|
|
379
|
+
if password.blank?
|
|
380
|
+
if hl.agree "No password (y/n)?"
|
|
381
|
+
return nil
|
|
382
|
+
else
|
|
383
|
+
return prompt_for_password
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
158
387
|
confirmation = hl.ask("Confirm the password: "){ |q| q.echo = false }
|
|
159
388
|
|
|
160
389
|
raise "Password does not match confirmation" unless password == confirmation
|
|
@@ -21,12 +21,61 @@
|
|
|
21
21
|
|
|
22
22
|
class Conjur::Command::Bootstrap < Conjur::Command
|
|
23
23
|
desc "Create initial users, groups, and permissions"
|
|
24
|
+
long_desc %Q(When you launch a new Conjur master server, it contains only one login: the "admin" user.
|
|
25
|
+
The bootstrap command will finish the setup of a new Conjur system by creating other essential records.
|
|
26
|
+
|
|
27
|
+
Actions performed by "bootstrap" include:
|
|
28
|
+
|
|
29
|
+
* Creation of a group called "security_admin".
|
|
30
|
+
|
|
31
|
+
* Giving the "security_admin" the power to manage public keys.
|
|
32
|
+
|
|
33
|
+
* Creation of a user called "attic", which will be the owner of retired records.
|
|
34
|
+
|
|
35
|
+
* Storing the "attic" user's API key in a variable called "conjur/users/attic/api-key".
|
|
36
|
+
|
|
37
|
+
* (optional) Create a new user who will be made a member and admin of the "security_admin" group.
|
|
38
|
+
|
|
39
|
+
* (optional) If a new user was created, login as that user.
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Determines whether the current logged-in user is sufficiently powerful to perform bootstrap.
|
|
43
|
+
# This is currently determined by detecting whether the logged-in role:
|
|
44
|
+
#
|
|
45
|
+
# * Is a user
|
|
46
|
+
# * Has admin privilege on the security_admin group role
|
|
47
|
+
# * Is an owner of the security_admin group resource
|
|
48
|
+
#
|
|
49
|
+
# The admin user will always satisfy these conditions, unless they are revoked for some reason.
|
|
50
|
+
# Other users created by the bootstrap command will (typically) also have these powers.
|
|
51
|
+
def self.security_admin_manager? api
|
|
52
|
+
username = api.username
|
|
53
|
+
user = if username.index('/')
|
|
54
|
+
nil
|
|
55
|
+
else
|
|
56
|
+
api.user(username)
|
|
57
|
+
end
|
|
58
|
+
security_admin = api.group("security_admin")
|
|
59
|
+
memberships = user.role.memberships.map(&:roleid) if user
|
|
60
|
+
begin
|
|
61
|
+
# The user exists
|
|
62
|
+
# The security_admin group exists
|
|
63
|
+
# The user has a role which is admin of the security_admin role
|
|
64
|
+
# The user has the role which owns the security_admin resource
|
|
65
|
+
user &&
|
|
66
|
+
security_admin.exists? &&
|
|
67
|
+
security_admin.role.members.find{|m| memberships.member?(m.member.roleid) && m.admin_option} &&
|
|
68
|
+
memberships.member?(security_admin.resource.ownerid)
|
|
69
|
+
rescue RestClient::Forbidden
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
24
73
|
|
|
25
74
|
Conjur::CLI.command :bootstrap do |c|
|
|
26
75
|
c.action do |global_options,options,args|
|
|
27
76
|
require 'highline/import'
|
|
28
77
|
|
|
29
|
-
exit_now! "You must be
|
|
78
|
+
exit_now! "You must be an administrator to bootstrap Conjur" unless security_admin_manager?(api)
|
|
30
79
|
|
|
31
80
|
if (security_admin = api.group("security_admin")).exists?
|
|
32
81
|
puts "Group 'security_admin' exists"
|
|
@@ -35,16 +84,22 @@ class Conjur::Command::Bootstrap < Conjur::Command
|
|
|
35
84
|
security_admin = api.create_group("security_admin")
|
|
36
85
|
end
|
|
37
86
|
|
|
38
|
-
|
|
39
|
-
|
|
87
|
+
security_admin.resource.give_to(security_admin) unless security_admin.resource.ownerid == security_admin.role.roleid
|
|
88
|
+
|
|
89
|
+
key_managers = api.group("pubkeys-1.0/key-managers")
|
|
90
|
+
unless security_admin.role.memberships.map(&:roleid).member?(key_managers.role.roleid)
|
|
91
|
+
puts "Permitting group 'security_admin' to manage public keys"
|
|
92
|
+
key_managers.add_member security_admin, admin_option: true
|
|
93
|
+
end
|
|
40
94
|
|
|
41
95
|
security_administrators = security_admin.role.members.select{|m| m.member.roleid.split(':')[1..-1] != [ 'user', 'admin'] }
|
|
42
96
|
puts "Current 'security_admin' members are : #{security_administrators.map{|m| m.member.roleid.split(':')[-1]}.join(', ')}" unless security_administrators.blank?
|
|
97
|
+
created_user = nil
|
|
43
98
|
if security_administrators.empty? || agree("Create a new security_admin? (answer 'y' or 'yes'):")
|
|
44
99
|
username = ask("Enter #{security_administrators.empty? ? 'your' : 'the'} username:")
|
|
45
100
|
password = prompt_for_password
|
|
46
101
|
puts "Creating user '#{username}'"
|
|
47
|
-
user = api.create_user(username, password: password)
|
|
102
|
+
created_user = user = api.create_user(username, password: password)
|
|
48
103
|
Conjur::API.new_from_key(user.login, password).user(user.login).resource.give_to security_admin
|
|
49
104
|
puts "User created"
|
|
50
105
|
puts "Making '#{username}' a member and admin of group 'security_admin'"
|
|
@@ -53,11 +108,22 @@ class Conjur::Command::Bootstrap < Conjur::Command
|
|
|
53
108
|
puts "Adminship granted"
|
|
54
109
|
end
|
|
55
110
|
|
|
56
|
-
|
|
57
|
-
|
|
111
|
+
attic_user_name = "attic"
|
|
112
|
+
if (attic = api.user(attic_user_name)).exists?
|
|
113
|
+
puts "User '#{attic_user_name}' already exists"
|
|
58
114
|
else
|
|
59
|
-
puts "Creating user '
|
|
60
|
-
attic = api.create_user(
|
|
115
|
+
puts "Creating user '#{attic_user_name}' to own retired records"
|
|
116
|
+
attic = api.create_user(attic_user_name)
|
|
117
|
+
api.create_variable "text/plain",
|
|
118
|
+
"conjur-api-key",
|
|
119
|
+
id: "conjur/users/#{attic_user_name}/api-key",
|
|
120
|
+
value: attic.api_key,
|
|
121
|
+
ownerid: security_admin.role.roleid
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if created_user && agree("Login as user '#{created_user.login}'? (answer 'y' or 'yes'):")
|
|
125
|
+
Conjur::Authn.fetch_credentials(username: created_user.login, password: created_user.api_key)
|
|
126
|
+
puts "Logged in as '#{created_user.login}'"
|
|
61
127
|
end
|
|
62
128
|
end
|
|
63
129
|
end
|
|
@@ -37,12 +37,34 @@ class Conjur::Command::Groups < Conjur::Command
|
|
|
37
37
|
|
|
38
38
|
acting_as_option(c)
|
|
39
39
|
|
|
40
|
-
c
|
|
41
|
-
id = require_arg(args, 'id')
|
|
40
|
+
interactive_option c
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
c.action do |global_options,options,args|
|
|
43
|
+
id = args.shift
|
|
44
|
+
|
|
45
|
+
interactive = options[:interactive] || id.blank?
|
|
46
|
+
|
|
47
|
+
groupid = options[:ownerid]
|
|
48
|
+
gidnumber = options[:gidnumber]
|
|
49
|
+
|
|
50
|
+
if interactive
|
|
51
|
+
id ||= prompt_for_id :group
|
|
52
|
+
|
|
53
|
+
groupid ||= prompt_for_group
|
|
54
|
+
gidnumber ||= prompt_for_gidnumber
|
|
55
|
+
|
|
56
|
+
prompt_to_confirm :group, {
|
|
57
|
+
"Id" => id,
|
|
58
|
+
"Owner" => groupid,
|
|
59
|
+
"Gidnumber" => gidnumber
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
group_options = { }
|
|
64
|
+
group_options[:ownerid] = groupid if groupid
|
|
65
|
+
group_options[:gidnumber] = gidnumber.to_i unless gidnumber.blank?
|
|
66
|
+
|
|
67
|
+
group = api.create_group(id, group_options)
|
|
46
68
|
display(group, options)
|
|
47
69
|
end
|
|
48
70
|
end
|
|
@@ -92,16 +114,18 @@ class Conjur::Command::Groups < Conjur::Command
|
|
|
92
114
|
group.desc "Decommission a group"
|
|
93
115
|
group.arg_name "id"
|
|
94
116
|
group.command :retire do |c|
|
|
117
|
+
retire_options c
|
|
118
|
+
|
|
95
119
|
c.action do |global_options,options,args|
|
|
96
120
|
id = require_arg(args, 'id')
|
|
97
121
|
|
|
98
122
|
group = api.group(id)
|
|
99
123
|
|
|
124
|
+
validate_retire_privileges group, options
|
|
125
|
+
|
|
100
126
|
retire_resource group
|
|
101
127
|
retire_role group
|
|
102
|
-
|
|
103
|
-
puts "Giving ownership to 'attic'"
|
|
104
|
-
group.resource.give_to api.user('attic')
|
|
128
|
+
give_away_resource group, options
|
|
105
129
|
|
|
106
130
|
puts "Group retired"
|
|
107
131
|
end
|
|
@@ -168,7 +192,9 @@ class Conjur::Command::Groups < Conjur::Command
|
|
|
168
192
|
end
|
|
169
193
|
|
|
170
194
|
end
|
|
171
|
-
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.prompt_for_gidnumber
|
|
198
|
+
prompt_for_idnumber "gid number"
|
|
172
199
|
end
|
|
173
200
|
end
|
|
174
|
-
|
data/lib/conjur/command/hosts.rb
CHANGED
|
@@ -58,10 +58,14 @@ class Conjur::Command::Hosts < Conjur::Command
|
|
|
58
58
|
hosts.desc "Decommission a host"
|
|
59
59
|
hosts.arg_name "id"
|
|
60
60
|
hosts.command :retire do |c|
|
|
61
|
+
retire_options c
|
|
62
|
+
|
|
61
63
|
c.action do |global_options,options,args|
|
|
62
64
|
id = require_arg(args, 'id')
|
|
63
65
|
|
|
64
66
|
host = api.host(id)
|
|
67
|
+
|
|
68
|
+
validate_retire_privileges host, options
|
|
65
69
|
|
|
66
70
|
host_layer_roles(host).each do |layer|
|
|
67
71
|
puts "Removing from layer #{layer.id}"
|
|
@@ -70,9 +74,7 @@ class Conjur::Command::Hosts < Conjur::Command
|
|
|
70
74
|
|
|
71
75
|
retire_resource host
|
|
72
76
|
retire_role host
|
|
73
|
-
|
|
74
|
-
puts "Giving ownership to 'attic'"
|
|
75
|
-
host.resource.give_to api.user('attic')
|
|
77
|
+
give_away_resource host, options
|
|
76
78
|
|
|
77
79
|
puts "Host retired"
|
|
78
80
|
end
|
|
@@ -47,17 +47,39 @@ class Conjur::Command::Pubkeys < Conjur::Command
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
pubkeys.desc "Add a public key for a user"
|
|
50
|
-
pubkeys.
|
|
50
|
+
pubkeys.long_desc %Q(Adds a public key for a user. The username is a required argument of this method.
|
|
51
|
+
|
|
52
|
+
The public key itself may be provided in several ways.
|
|
53
|
+
|
|
54
|
+
1. After the username argument, the public key can be provided as a literal (quoted) string.
|
|
55
|
+
|
|
56
|
+
2. After the username argument, the path to the public key file can be provided with a leading @ character.
|
|
57
|
+
|
|
58
|
+
3. If the only argument to this command is the username, the key will be read from stdin.
|
|
59
|
+
|
|
60
|
+
4. If you provide the -i (interactive) command option, you'll be prompted for the public key
|
|
61
|
+
)
|
|
62
|
+
pubkeys.arg_name "username key?"
|
|
51
63
|
pubkeys.command :add do |c|
|
|
64
|
+
interactive_option c
|
|
65
|
+
|
|
52
66
|
c.action do |global_options, options, args|
|
|
67
|
+
options[:interactive] = $stdin.isatty if options[:interactive].nil?
|
|
53
68
|
username = require_arg args, "username"
|
|
54
69
|
if key = args.shift
|
|
55
70
|
if /^@(.+)$/ =~ key
|
|
56
71
|
key = File.read(File.expand_path($1))
|
|
57
72
|
end
|
|
58
73
|
else
|
|
59
|
-
key =
|
|
74
|
+
key = if options[:interactive]
|
|
75
|
+
prompt_for_public_key
|
|
76
|
+
else
|
|
77
|
+
STDIN.read.strip.tap do |k|
|
|
78
|
+
exit_now! "Invalid public key format" unless validate_public_key(k)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
60
81
|
end
|
|
82
|
+
fail "Cancelled by the user" if key.blank?
|
|
61
83
|
api.add_public_key username, key
|
|
62
84
|
puts "Public key '#{key.split(' ').last}' added"
|
|
63
85
|
end
|
|
@@ -74,4 +96,4 @@ class Conjur::Command::Pubkeys < Conjur::Command
|
|
|
74
96
|
end
|
|
75
97
|
end
|
|
76
98
|
end
|
|
77
|
-
end
|
|
99
|
+
end
|
|
@@ -138,12 +138,22 @@ class Conjur::Command::Resources < Conjur::Command
|
|
|
138
138
|
resource.desc "Set an annotation on a resource"
|
|
139
139
|
resource.arg_name "resource-id name value"
|
|
140
140
|
resource.command :annotate do |c|
|
|
141
|
+
interactive_option c
|
|
142
|
+
|
|
141
143
|
c.action do |global_options, options, args|
|
|
142
144
|
id = full_resource_id require_arg(args, 'resource-id')
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
|
|
146
|
+
annotations = if options[:interactive]
|
|
147
|
+
prompt_for_annotations
|
|
148
|
+
else
|
|
149
|
+
name = require_arg args, 'name'
|
|
150
|
+
value = require_arg args, 'value'
|
|
151
|
+
{ name => value }
|
|
152
|
+
end
|
|
153
|
+
unless annotations.blank?
|
|
154
|
+
api.resource(id).annotations.merge!(annotations)
|
|
155
|
+
puts "Set annotations #{annotations.keys} for resource '#{id}'"
|
|
156
|
+
end
|
|
147
157
|
end
|
|
148
158
|
end
|
|
149
159
|
|
data/lib/conjur/command/users.rb
CHANGED
|
@@ -35,20 +35,54 @@ class Conjur::Command::Users < Conjur::Command
|
|
|
35
35
|
|
|
36
36
|
acting_as_option(c)
|
|
37
37
|
|
|
38
|
-
c
|
|
39
|
-
login = require_arg(args, 'login')
|
|
38
|
+
interactive_option c
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
c.action do |global_options,options,args|
|
|
41
|
+
login = args.shift
|
|
42
|
+
|
|
43
|
+
interactive = options[:interactive] || login.blank?
|
|
44
|
+
|
|
45
|
+
groupid = options[:ownerid]
|
|
46
|
+
uidnumber = options[:uidnumber]
|
|
47
|
+
password = nil
|
|
48
|
+
exit_now! "uidnumber should be integer" unless uidnumber.blank? || /\d+/ =~ uidnumber
|
|
49
|
+
|
|
50
|
+
if interactive
|
|
51
|
+
login ||= prompt_for_id :user, "login name"
|
|
52
|
+
|
|
53
|
+
groupid ||= prompt_for_group hint: "press enter to have the user own their own record"
|
|
54
|
+
uidnumber ||= prompt_for_uidnumber
|
|
55
|
+
password = prompt_for_password unless options[:"no-password"]
|
|
56
|
+
|
|
57
|
+
attributes = {
|
|
58
|
+
"Login" => login,
|
|
59
|
+
"Owner" => groupid,
|
|
60
|
+
"UID Number" => uidnumber
|
|
61
|
+
}
|
|
62
|
+
attributes["Password"] = "********" unless password.blank?
|
|
63
|
+
prompt_to_confirm :user, attributes
|
|
45
64
|
end
|
|
46
|
-
|
|
47
|
-
if options[:p]
|
|
48
|
-
|
|
65
|
+
|
|
66
|
+
if options[:p] && password.blank?
|
|
67
|
+
password = prompt_for_password
|
|
49
68
|
end
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
user_options = { }
|
|
71
|
+
user_options[:ownerid] = groupid if groupid
|
|
72
|
+
user_options[:uidnumber] = uidnumber.to_i if uidnumber
|
|
73
|
+
user_options[:password] = password if password
|
|
74
|
+
user = api.create_user(login, user_options)
|
|
75
|
+
|
|
76
|
+
puts "User created"
|
|
77
|
+
display user
|
|
78
|
+
|
|
79
|
+
if interactive
|
|
80
|
+
public_key = prompt_for_public_key
|
|
81
|
+
if public_key
|
|
82
|
+
api.add_public_key user.login, public_key
|
|
83
|
+
puts "Public key added"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
52
86
|
end
|
|
53
87
|
end
|
|
54
88
|
|
|
@@ -64,16 +98,18 @@ class Conjur::Command::Users < Conjur::Command
|
|
|
64
98
|
user.desc "Decommission a user"
|
|
65
99
|
user.arg_name "id"
|
|
66
100
|
user.command :retire do |c|
|
|
101
|
+
retire_options c
|
|
102
|
+
|
|
67
103
|
c.action do |global_options,options,args|
|
|
68
104
|
id = require_arg(args, 'id')
|
|
69
105
|
|
|
70
106
|
user = api.user(id)
|
|
71
107
|
|
|
108
|
+
validate_retire_privileges user, options
|
|
109
|
+
|
|
72
110
|
retire_resource user
|
|
73
111
|
retire_role user
|
|
74
|
-
|
|
75
|
-
puts "Giving ownership to 'attic'"
|
|
76
|
-
user.resource.give_to api.user('attic')
|
|
112
|
+
give_away_resource user, options
|
|
77
113
|
|
|
78
114
|
puts "User retired"
|
|
79
115
|
end
|
|
@@ -125,7 +161,9 @@ class Conjur::Command::Users < Conjur::Command
|
|
|
125
161
|
display api.find_users(uidnumber: uidnumber)
|
|
126
162
|
end
|
|
127
163
|
end
|
|
128
|
-
|
|
129
164
|
end
|
|
130
|
-
|
|
165
|
+
|
|
166
|
+
def self.prompt_for_uidnumber
|
|
167
|
+
prompt_for_idnumber "uid number"
|
|
168
|
+
end
|
|
131
169
|
end
|
|
@@ -34,55 +34,57 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
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
|
|
|
37
|
-
acting_as_option
|
|
38
|
-
|
|
39
|
-
c
|
|
40
|
-
c.desc 'Create variable interactively'
|
|
41
|
-
c.switch [:i, :'interactive']
|
|
37
|
+
acting_as_option c
|
|
38
|
+
|
|
39
|
+
annotate_option c
|
|
42
40
|
|
|
41
|
+
interactive_option c
|
|
42
|
+
|
|
43
43
|
c.action do |global_options,options, args|
|
|
44
44
|
@default_mime_type = c.flags[:m].default_value
|
|
45
45
|
@default_kind = c.flags[:k].default_value
|
|
46
46
|
|
|
47
47
|
id = args.shift unless args.empty?
|
|
48
|
-
|
|
49
48
|
value = args.shift unless args.empty?
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
groupid = options[:
|
|
54
|
-
mime_type = options
|
|
55
|
-
kind = options
|
|
56
|
-
value ||= options
|
|
57
|
-
|
|
58
|
-
options
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
exit_now! "Received conflicting value arguments" if value && options[:value]
|
|
51
|
+
|
|
52
|
+
groupid = options[:ownerid]
|
|
53
|
+
mime_type = options[:m]
|
|
54
|
+
kind = options[:k]
|
|
55
|
+
value ||= options[:v]
|
|
56
|
+
interactive = options[:interactive] || id.blank?
|
|
57
|
+
annotate = options[:annotate]
|
|
58
|
+
|
|
59
|
+
exit_now! "Received --annotate option without --interactive" if annotate && !interactive
|
|
60
|
+
|
|
63
61
|
annotations = {}
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
# If the user asked for interactive mode, or he didn't specify and id
|
|
63
|
+
# prompt for any missing options.
|
|
64
|
+
if interactive
|
|
65
|
+
id ||= prompt_for_id :variable
|
|
66
|
+
|
|
70
67
|
groupid ||= prompt_for_group
|
|
71
68
|
|
|
72
69
|
kind = prompt_for_kind if !kind || kind == @default_kind
|
|
70
|
+
|
|
71
|
+
mime_type = prompt_for_mime_type if mime_type.blank? || mime_type == @default_mime_type
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
annotations = prompt_for_annotations
|
|
73
|
+
annotations = prompt_for_annotations if annotate
|
|
77
74
|
|
|
78
75
|
value ||= prompt_for_value
|
|
76
|
+
|
|
77
|
+
prompt_to_confirm :variable, "Id" => id,
|
|
78
|
+
"Kind" => kind,
|
|
79
|
+
"MIME type" => mime_type,
|
|
80
|
+
"Owner" => groupid,
|
|
81
|
+
"Value" => value
|
|
79
82
|
end
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
var = api.create_variable(mime_type, kind, options)
|
|
84
|
+
variable_options = { id: id }
|
|
85
|
+
variable_options[:ownerid] = groupid if groupid
|
|
86
|
+
variable_options[:value] = value unless value.blank?
|
|
87
|
+
var = api.create_variable(mime_type, kind, variable_options)
|
|
86
88
|
api.resource(var).annotations.merge!(annotations) if annotations && !annotations.empty?
|
|
87
89
|
display(var, options)
|
|
88
90
|
end
|
|
@@ -100,15 +102,17 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
100
102
|
var.desc "Decommission a variable"
|
|
101
103
|
var.arg_name "id"
|
|
102
104
|
var.command :retire do |c|
|
|
105
|
+
retire_options c
|
|
106
|
+
|
|
103
107
|
c.action do |global_options,options,args|
|
|
104
108
|
id = require_arg(args, 'id')
|
|
105
109
|
|
|
106
110
|
variable = api.variable(id)
|
|
111
|
+
|
|
112
|
+
validate_retire_privileges variable, options
|
|
107
113
|
|
|
108
114
|
retire_resource variable
|
|
109
|
-
|
|
110
|
-
puts "Giving ownership to 'attic'"
|
|
111
|
-
variable.resource.give_to api.user('attic')
|
|
115
|
+
give_away_resource variable, options
|
|
112
116
|
|
|
113
117
|
puts "Variable retired"
|
|
114
118
|
end
|
|
@@ -149,58 +153,24 @@ class Conjur::Command::Variables < Conjur::Command
|
|
|
149
153
|
$stdout.write api.variable(id).value(options[:version])
|
|
150
154
|
end
|
|
151
155
|
end
|
|
152
|
-
|
|
153
156
|
end
|
|
154
|
-
|
|
155
|
-
def self.prompt_for_id
|
|
156
|
-
highline.ask('Enter the id: ')
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def self.prompt_for_group
|
|
160
|
-
highline.ask('Enter the group: ', ->(name) { @group && @group.roleid } ) do |q|
|
|
161
|
-
q.validate = ->(name) do
|
|
162
|
-
name.empty? || (@group = api.group(name)).exists?
|
|
163
|
-
end
|
|
164
|
-
q.responses[:not_valid] = "Group '<%= @answer %>' doesn't exist, or you don't have permission to use it"
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
157
|
+
|
|
168
158
|
def self.prompt_for_kind
|
|
169
159
|
highline.ask('Enter the kind: ') {|q| q.default = @default_kind }
|
|
170
160
|
end
|
|
171
161
|
|
|
172
162
|
def self.prompt_for_mime_type
|
|
173
|
-
highline.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
until (name = highline.ask('annotation name: ')).empty?
|
|
180
|
-
annotations[name] = read_till_eof('annotation value (^D to finish):')
|
|
163
|
+
highline.choose do |menu|
|
|
164
|
+
menu.prompt = 'Enter the MIME type: '
|
|
165
|
+
menu.choice @default_mime_type
|
|
166
|
+
menu.choices *%w(application/json application/xml application/x-yaml application/x-pem-file)
|
|
167
|
+
menu.choice "other", nil do |c|
|
|
168
|
+
@highline.ask('Enter a custom mime type: ')
|
|
181
169
|
end
|
|
182
170
|
end
|
|
183
171
|
end
|
|
184
172
|
|
|
185
173
|
def self.prompt_for_value
|
|
186
|
-
read_till_eof('Enter the value (^D on its own line to finish):')
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def self.highline
|
|
190
|
-
require 'highline'
|
|
191
|
-
@highline ||= HighLine.new($stdin,$stderr)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def self.read_till_eof(prompt = nil)
|
|
195
|
-
highline.say(prompt) if prompt
|
|
196
|
-
[].tap do |lines|
|
|
197
|
-
loop do
|
|
198
|
-
begin
|
|
199
|
-
lines << highline.ask('')
|
|
200
|
-
rescue EOFError
|
|
201
|
-
break
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end.join("\n")
|
|
174
|
+
read_till_eof('Enter the secret value (^D on its own line to finish):')
|
|
205
175
|
end
|
|
206
176
|
end
|
data/lib/conjur/version.rb
CHANGED
|
@@ -61,6 +61,8 @@ describe Conjur::Command::Pubkeys, logged_in: true do
|
|
|
61
61
|
let(:stdin_contents){ "ssh-rsa blahblah keyname" }
|
|
62
62
|
it "calls api.add_public_key('alice', stdin) and prints the key name" do
|
|
63
63
|
expect(STDIN).to receive(:read).and_return(stdin_contents)
|
|
64
|
+
allow(STDIN).to receive(:isatty).and_return(false)
|
|
65
|
+
expect(described_class).to receive(:validate_public_key).and_return(true)
|
|
64
66
|
expect(described_class.api).to receive(:add_public_key).with('alice', stdin_contents)
|
|
65
67
|
expect{ invoke }.to write("Public key 'keyname' added")
|
|
66
68
|
end
|
|
@@ -149,4 +149,14 @@ describe Conjur::Command::Resources, logged_in: true do
|
|
|
149
149
|
expect(JSON.parse( expect { invoke }.to write )).to eq(roles_list)
|
|
150
150
|
end
|
|
151
151
|
end
|
|
152
|
+
|
|
153
|
+
context "interactivity" do
|
|
154
|
+
subject { Conjur::Command::Resources }
|
|
155
|
+
describe_command 'resource:annotate -i #{KIND}:#{ID}' do
|
|
156
|
+
it {
|
|
157
|
+
is_expected.to receive(:prompt_for_annotations)
|
|
158
|
+
invoke_silently
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
end
|
|
152
162
|
end
|
|
@@ -12,10 +12,10 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
12
12
|
end
|
|
13
13
|
let(:id) { 'the-id' }
|
|
14
14
|
let(:variable) { post_response(id) }
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
let
|
|
18
|
-
let
|
|
15
|
+
let(:group) { nil }
|
|
16
|
+
let(:annotation) { {} }
|
|
17
|
+
let(:value) { 'the-value' }
|
|
18
|
+
let(:full_payload) { base_payload }
|
|
19
19
|
|
|
20
20
|
context 'when there are command-line errors' do
|
|
21
21
|
describe_command "variable:create -v the-value-1 the-id the-value-2" do
|
|
@@ -25,16 +25,34 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
context "-a without -i" do
|
|
29
|
+
describe_command 'variable:create -a the-id' do
|
|
30
|
+
it "is an error" do
|
|
31
|
+
expect { invoke }.to raise_error("Received --annotate option without --interactive")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'non-interactive' do
|
|
37
|
+
describe_command "variable:create the-id" do
|
|
38
|
+
it "is non-interactive" do
|
|
39
|
+
allow(Conjur::Command::Variables).to receive(:prompt_for_id).and_raise("Unexpected prompt for id")
|
|
40
|
+
expect(RestClient::Request).to receive(:execute).and_return(variable)
|
|
41
|
+
invoke
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
28
46
|
context 'when there are no command-line errors' do
|
|
29
|
-
|
|
30
47
|
before do
|
|
48
|
+
allow(Conjur::Command::Variables).to receive(:prompt_to_confirm) { "yes"}
|
|
31
49
|
allow(Conjur::Command::Variables).to receive(:prompt_for_id) { id }
|
|
32
50
|
allow(Conjur::Command::Variables).to receive(:prompt_for_group) { group }
|
|
33
51
|
allow(Conjur::Command::Variables).to receive(:prompt_for_kind) { kind }
|
|
34
52
|
allow(Conjur::Command::Variables).to receive(:prompt_for_mime_type) { mime_type }
|
|
35
53
|
allow(Conjur::Command::Variables).to receive(:prompt_for_annotations) { annotation }
|
|
36
54
|
allow(Conjur::Command::Variables).to receive(:prompt_for_value) { value }
|
|
37
|
-
|
|
55
|
+
|
|
38
56
|
expect(RestClient::Request).to receive(:execute).with({
|
|
39
57
|
method: :post,
|
|
40
58
|
url: collection_url,
|
|
@@ -43,29 +61,42 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
43
61
|
}.merge(cert_store_options)).and_return(variable)
|
|
44
62
|
end
|
|
45
63
|
|
|
46
|
-
describe_command "variable:create the-different-
|
|
47
|
-
let (:
|
|
64
|
+
describe_command "variable:create the-id the-different-value" do
|
|
65
|
+
let (:value) { 'the-different-value' }
|
|
48
66
|
it "propagates the user-assigned id" do
|
|
49
|
-
expect { invoke }.to write({ id: 'the-
|
|
67
|
+
expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
|
|
50
68
|
end
|
|
51
69
|
end
|
|
52
70
|
|
|
53
|
-
describe_command "variable:create the-id
|
|
54
|
-
let
|
|
55
|
-
|
|
71
|
+
describe_command "variable:create the-id" do
|
|
72
|
+
let(:value) { "" }
|
|
73
|
+
let(:full_payload) {
|
|
74
|
+
base_payload.dup.tap do |m|
|
|
75
|
+
m.delete_if{|k,_| k == :value}
|
|
76
|
+
end
|
|
77
|
+
}
|
|
78
|
+
it "will propagate the user-assigned id without a value" do
|
|
56
79
|
expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
|
|
57
80
|
end
|
|
58
81
|
end
|
|
59
82
|
|
|
83
|
+
let(:base_payload) do
|
|
84
|
+
{ id: id, value: value, mime_type: mime_type, kind: kind }.tap do |t|
|
|
85
|
+
group && t.merge(ownerid: group)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
60
89
|
describe_command "variable:create -m application/json" do
|
|
61
|
-
let
|
|
90
|
+
let(:mime_type) { 'application/json' }
|
|
91
|
+
let(:payload) { valueless_payload }
|
|
62
92
|
it "propagates the user-assigned MIME type" do
|
|
63
93
|
expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
|
|
64
94
|
end
|
|
65
95
|
end
|
|
66
96
|
|
|
67
97
|
describe_command "variable:create -k password" do
|
|
68
|
-
let
|
|
98
|
+
let(:kind) { 'password' }
|
|
99
|
+
let(:payload) { valueless_payload }
|
|
69
100
|
it "propagates the user-assigned kind" do
|
|
70
101
|
expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
|
|
71
102
|
end
|
|
@@ -84,25 +115,22 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
84
115
|
it { is_expected.to receive(:prompt_for_group) }
|
|
85
116
|
it { is_expected.to receive(:prompt_for_kind) }
|
|
86
117
|
it { is_expected.to receive(:prompt_for_mime_type) }
|
|
87
|
-
it { is_expected.
|
|
118
|
+
it { is_expected.not_to receive(:prompt_for_annotations) }
|
|
88
119
|
it { is_expected.to receive(:prompt_for_value) }
|
|
89
120
|
end
|
|
90
121
|
|
|
91
|
-
describe_command 'variable:create the-id' do
|
|
92
|
-
it { is_expected.not_to receive(:prompt_for_id) }
|
|
93
|
-
end
|
|
94
|
-
|
|
95
122
|
describe_command 'variable:create the-id the-value' do
|
|
123
|
+
it { is_expected.not_to receive(:prompt_for_id) }
|
|
96
124
|
it { is_expected.not_to receive(:prompt_for_value) }
|
|
97
125
|
end
|
|
98
126
|
|
|
99
127
|
describe_command 'variable:create -m application/json' do
|
|
100
|
-
let
|
|
128
|
+
let(:mime_type) { 'application/json' }
|
|
101
129
|
it { is_expected.not_to receive(:prompt_for_mime_type) }
|
|
102
130
|
end
|
|
103
131
|
|
|
104
132
|
describe_command 'variable:create -k password' do
|
|
105
|
-
let
|
|
133
|
+
let(:kind) { 'password' }
|
|
106
134
|
it { is_expected.not_to receive(:prompt_for_kind) }
|
|
107
135
|
end
|
|
108
136
|
|
|
@@ -119,7 +147,7 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
119
147
|
}.merge(cert_store_options)).and_return(OpenStruct.new(headers: {}, body: '{}'))
|
|
120
148
|
end
|
|
121
149
|
|
|
122
|
-
let
|
|
150
|
+
let(:full_payload) { base_payload.merge(ownerid: 'the-account:group:the-group') }
|
|
123
151
|
|
|
124
152
|
it { is_expected.not_to receive(:prompt_for_group) }
|
|
125
153
|
end
|
|
@@ -133,25 +161,32 @@ describe Conjur::Command::Variables, logged_in: true do
|
|
|
133
161
|
}.merge(cert_store_options)).and_return(OpenStruct.new(headers: {}, body: '{}'))
|
|
134
162
|
end
|
|
135
163
|
|
|
136
|
-
let
|
|
164
|
+
let(:full_payload) { base_payload.merge(ownerid: 'the-account:group:the-group') }
|
|
137
165
|
|
|
138
166
|
it { is_expected.not_to receive(:prompt_for_group) }
|
|
139
167
|
end
|
|
140
168
|
|
|
141
169
|
end
|
|
142
170
|
|
|
143
|
-
context "
|
|
171
|
+
context "explicit interactivity" do
|
|
144
172
|
describe_command 'variable:create -i the-id the-value' do
|
|
145
173
|
it { is_expected.not_to receive(:prompt_for_id) }
|
|
146
174
|
it { is_expected.not_to receive(:prompt_for_value) }
|
|
147
175
|
it { is_expected.to receive(:prompt_for_group) }
|
|
148
176
|
it { is_expected.to receive(:prompt_for_mime_type) }
|
|
149
177
|
it { is_expected.to receive(:prompt_for_kind) }
|
|
150
|
-
it { is_expected.
|
|
178
|
+
it { is_expected.not_to receive(:prompt_for_annotations) }
|
|
151
179
|
end
|
|
152
180
|
end
|
|
153
181
|
|
|
182
|
+
context "interactive annotations" do
|
|
183
|
+
describe_command 'variable:create -a' do
|
|
184
|
+
it { is_expected.to receive(:prompt_for_annotations) }
|
|
185
|
+
end
|
|
186
|
+
describe_command 'variable:create -ia the-id' do
|
|
187
|
+
it { is_expected.to receive(:prompt_for_annotations) }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
154
190
|
end
|
|
155
|
-
|
|
156
191
|
end
|
|
157
192
|
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.
|
|
4
|
+
version: 4.25.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-05-
|
|
12
|
+
date: 2015-05-30 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activesupport
|
|
@@ -269,6 +269,7 @@ files:
|
|
|
269
269
|
- CHANGELOG.md
|
|
270
270
|
- Gemfile
|
|
271
271
|
- LICENSE
|
|
272
|
+
- PUBLISH.md
|
|
272
273
|
- README.md
|
|
273
274
|
- Rakefile
|
|
274
275
|
- bin/_conjur_completions
|