conjur-cli 4.28.2 → 4.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +8 -0
  3. data/.gitignore +2 -0
  4. data/.overcommit.yml +10 -0
  5. data/.rubocop.yml +14 -0
  6. data/CHANGELOG.md +16 -0
  7. data/Dockerfile +10 -0
  8. data/Gemfile +2 -0
  9. data/Rakefile +1 -1
  10. data/acceptance-features/audit/audit_event_send.feature +46 -43
  11. data/acceptance-features/audit/send.feature +0 -19
  12. data/acceptance-features/authentication/login.feature +0 -2
  13. data/acceptance-features/authentication/logout.feature +0 -3
  14. data/acceptance-features/authorization/resource/check.feature +6 -4
  15. data/acceptance-features/authorization/resource/create.feature +4 -2
  16. data/acceptance-features/authorization/resource/exists.feature +8 -6
  17. data/acceptance-features/authorization/resource/give.feature +3 -1
  18. data/acceptance-features/authorization/resource/show.feature +3 -1
  19. data/acceptance-features/authorization/role/graph.feature +0 -1
  20. data/acceptance-features/conjurenv/check.feature +3 -10
  21. data/acceptance-features/conjurenv/run.feature +3 -3
  22. data/acceptance-features/conjurenv/template.feature +1 -1
  23. data/acceptance-features/directory/hostfactory/create.feature +13 -0
  24. data/acceptance-features/directory/hostfactory/tokens.feature +16 -0
  25. data/acceptance-features/directory/layer/retire.feature +43 -0
  26. data/acceptance-features/directory/user/update_password.feature +0 -1
  27. data/acceptance-features/directory/variable/value.feature +3 -2
  28. data/acceptance-features/dsl/policy_owner.feature +21 -7
  29. data/acceptance-features/dsl/resource_owner.feature +4 -4
  30. data/acceptance-features/pubkeys/add.feature +4 -2
  31. data/acceptance-features/pubkeys/names.feature +6 -3
  32. data/acceptance-features/pubkeys/show.feature +4 -2
  33. data/acceptance-features/step_definitions/{cli.rb → cli_steps.rb} +18 -4
  34. data/acceptance-features/step_definitions/user_steps.rb +13 -12
  35. data/acceptance-features/support/env.rb +0 -1
  36. data/acceptance-features/support/hooks.rb +11 -14
  37. data/acceptance-features/support/world.rb +16 -18
  38. data/build-deb.sh +19 -0
  39. data/ci/test.sh +19 -0
  40. data/conjur.gemspec +9 -12
  41. data/debify.sh +4 -0
  42. data/distrib/bin/_conjur +3 -0
  43. data/distrib/bin/conjur +3 -0
  44. data/distrib/bin/conjurize +3 -0
  45. data/distrib/bin/jsonfield +3 -0
  46. data/features/conjurize.feature +25 -25
  47. data/features/support/env.rb +5 -1
  48. data/features/support/hooks.rb +0 -1
  49. data/jenkins.sh +29 -1
  50. data/lib/conjur/cli.rb +27 -4
  51. data/lib/conjur/command.rb +36 -0
  52. data/lib/conjur/command/audit.rb +12 -0
  53. data/lib/conjur/command/bootstrap.rb +5 -9
  54. data/lib/conjur/command/host_factories.rb +187 -0
  55. data/lib/conjur/command/hosts.rb +82 -2
  56. data/lib/conjur/command/layers.rb +28 -0
  57. data/lib/conjur/command/resources.rb +1 -0
  58. data/lib/conjur/command/rspec/mock_services.rb +1 -1
  59. data/lib/conjur/command/server.rb +67 -0
  60. data/lib/conjur/command/users.rb +67 -12
  61. data/lib/conjur/command/variables.rb +101 -14
  62. data/lib/conjur/conjurize.rb +25 -69
  63. data/lib/conjur/conjurize/script.rb +133 -0
  64. data/lib/conjur/version.rb +1 -1
  65. data/publish.sh +6 -0
  66. data/spec/command/elevate_spec.rb +1 -1
  67. data/spec/command/host_factories_spec.rb +38 -0
  68. data/spec/command/hosts_spec.rb +86 -22
  69. data/spec/command/users_spec.rb +51 -3
  70. data/spec/command/variable_expiration_spec.rb +174 -0
  71. data/spec/command/variables_spec.rb +1 -1
  72. data/spec/conjurize_spec.rb +70 -0
  73. metadata +61 -64
@@ -2,14 +2,7 @@ require 'methadone'
2
2
  require 'json'
3
3
  require 'open-uri'
4
4
  require 'conjur/version.rb'
5
-
6
- def latest_conjur_release
7
- url = 'https://api.github.com/repos/conjur-cookbooks/conjur/releases'
8
- resp = open(url)
9
- json = JSON.parse(resp.read)
10
- latest = json[0]['assets'].select {|asset| asset['name'] =~ /conjur-v\d.\d.\d.tar.gz/}[0]
11
- latest['browser_download_url']
12
- end
5
+ require "conjur/conjurize/script"
13
6
 
14
7
  module Conjur
15
8
  class Conjurize
@@ -31,77 +24,37 @@ DESC
31
24
  else
32
25
  STDIN.read
33
26
  end
34
- host = JSON.parse input
35
27
 
36
- login = host['id'] or raise "No 'id' field in host JSON"
37
- api_key = host['api_key'] or raise "No 'api_key' field in host JSON"
28
+ puts generate JSON.parse input
29
+ end
30
+
31
+ def self.generate host
32
+ config = configuration host
38
33
 
39
- require 'conjur/cli'
34
+ if options[:json]
35
+ JSON.dump config
36
+ else
37
+ Script.generate config, options
38
+ end
39
+ end
40
+
41
+ def self.apply_client_config
42
+ require "conjur/cli"
40
43
  if conjur_config = options[:c]
41
44
  Conjur::Config.load [ conjur_config ]
42
45
  else
43
46
  Conjur::Config.load
44
47
  end
45
48
  Conjur::Config.apply
49
+ end
46
50
 
47
- conjur_cookbook_url = conjur_run_list = nil
48
-
49
- conjur_run_list = options[:"conjur-run-list"]
50
- conjur_cookbook_url = options[:"conjur-cookbook-url"]
51
- chef_executable = options[:"chef-executable"]
52
-
53
- if options[:ssh]
54
- conjur_run_list ||= "conjur"
55
- conjur_cookbook_url ||= latest_conjur_release()
56
- end
57
-
58
- sudo = lambda{|str|
59
- [ options[:sudo] ? "sudo -n" : nil, str ].compact.join(" ")
60
- }
61
-
62
- header = <<-HEADER
63
- #!/bin/sh
64
- set -e
65
-
66
- # Implementation note: 'tee' is used as a sudo-friendly 'cat' to populate a file with the contents provided below.
67
- HEADER
68
-
69
- configure_conjur = <<-CONFIGURE
70
- #{sudo.call 'tee'} /etc/conjur.conf > /dev/null << CONJUR_CONF
71
- account: #{Conjur.configuration.account}
72
- appliance_url: #{Conjur.configuration.appliance_url}
73
- cert_file: /etc/conjur-#{Conjur.configuration.account}.pem
74
- netrc_path: /etc/conjur.identity
75
- plugins: []
76
- CONJUR_CONF
77
-
78
- #{sudo.call 'tee'} /etc/conjur-#{Conjur.configuration.account}.pem > /dev/null << CONJUR_CERT
79
- #{File.read(Conjur.configuration.cert_file).strip}
80
- CONJUR_CERT
81
-
82
- #{sudo.call 'tee'} /etc/conjur.identity > /dev/null << CONJUR_IDENTITY
83
- machine #{Conjur.configuration.appliance_url}/authn
84
- login host/#{login}
85
- password #{api_key}
86
- CONJUR_IDENTITY
87
- #{sudo.call 'chmod'} 0600 /etc/conjur.identity
88
- CONFIGURE
89
-
90
- install_chef = if conjur_cookbook_url && !chef_executable
91
- %Q(curl -L https://www.opscode.com/chef/install.sh | #{sudo.call 'bash'})
92
- else
93
- nil
94
- end
95
-
96
- chef_executable ||= "chef-solo"
97
-
98
- run_chef = if conjur_cookbook_url
99
- %Q(#{sudo.call "#{chef_executable} -r #{conjur_cookbook_url} -o #{conjur_run_list}"})
100
- else
101
- nil
102
- end
51
+ def self.configuration host
52
+ apply_client_config
103
53
 
104
- puts [ header, configure_conjur, install_chef, run_chef ].compact.join("\n")
54
+ host.merge \
55
+ "account" => Conjur.configuration.account,
56
+ "appliance_url" => Conjur.configuration.appliance_url,
57
+ "certificate" => File.read(Conjur.configuration.cert_file).strip
105
58
  end
106
59
 
107
60
  on("-c CONJUR_CONFIG_FILE", "Overrides defaults (CONJURRC env var, ~/.conjurrc, /etc/conjur.conf).")
@@ -111,5 +64,8 @@ CONJUR_IDENTITY
111
64
  on("--sudo", "Indicates that all commands should be run via 'sudo'.")
112
65
  on("--conjur-cookbook-url NAME", "Overrides the default Chef cookbook URL for Conjur SSH.")
113
66
  on("--conjur-run-list RUNLIST", "Overrides the default Chef run list for Conjur SSH.")
67
+ on \
68
+ "--json",
69
+ "Don't generate the script, instead just dump the configuration as JSON"
114
70
  end
115
71
  end
@@ -0,0 +1,133 @@
1
+ require "json"
2
+ require "open-uri"
3
+
4
+ class Conjur::Conjurize
5
+ # generates a shell script to conjurize a host
6
+ class Script
7
+ COOKBOOK_RELEASES_URL =
8
+ "https://api.github.com/repos/conjur-cookbooks/conjur/releases".freeze
9
+
10
+ def self.latest_conjur_cookbook_release
11
+ json = JSON.parse open(COOKBOOK_RELEASES_URL).read
12
+ tarballs = json[0]["assets"].select do |asset|
13
+ asset["name"] =~ /conjur-v\d.\d.\d.tar.gz/
14
+ end
15
+ tarballs.first["browser_download_url"]
16
+ end
17
+
18
+ HEADER = <<-HEADER.freeze
19
+ #!/bin/sh
20
+ set -e
21
+
22
+ # Implementation note: 'tee' is used as a sudo-friendly 'cat' to populate a file with the contents provided below.
23
+ HEADER
24
+
25
+ def initialize options
26
+ @options = options
27
+ end
28
+
29
+ attr_reader :options
30
+
31
+ def sudo
32
+ @sudo ||= options["sudo"] ? ->(x) { "sudo -n #{x}" } : ->(x) { x }
33
+ end
34
+
35
+ # Generate a piece of shell to write to a file
36
+ # @param path [String] absolute path to write to
37
+ # @param content [String] contents to write
38
+ # @option options [String, Fixnum] :mode mode to apply to the file
39
+ def write_file path, content, options = {}
40
+ [
41
+ ((mode = options[:mode]) && set_mode(path, mode)),
42
+ [sudo["tee"], path, "> /dev/null << EOF"].join(" "),
43
+ content.strip,
44
+ "EOF\n"
45
+ ].compact.join("\n")
46
+ end
47
+
48
+ def set_mode path, mode
49
+ mode = mode.to_s(8) if mode.respond_to? :to_int
50
+ [
51
+ [sudo["touch"], path].join(" "),
52
+ [sudo["chmod"], mode, path].join(" ")
53
+ ].join("\n")
54
+ end
55
+
56
+ def self.generate configuration, options
57
+ new(options).generate configuration
58
+ end
59
+
60
+ def install_chef?
61
+ run_chef? && !options[:"chef-executable"]
62
+ end
63
+
64
+ def run_chef?
65
+ options.values_at(:ssh, :"conjur-run-list").any?
66
+ end
67
+
68
+ def chef_executable
69
+ options[:"chef-executable"] || "chef-solo"
70
+ end
71
+
72
+ def conjur_cookbook_url
73
+ options[:"conjur-cookbook-url"] || Script.latest_conjur_cookbook_release
74
+ end
75
+
76
+ def conjur_run_list
77
+ options[:"conjur-run-list"] || "conjur"
78
+ end
79
+
80
+ def chef_script
81
+ @chef_script ||= [
82
+ ("curl -L https://www.opscode.com/chef/install.sh | " + sudo["bash"] \
83
+ if install_chef?),
84
+ (sudo["#{chef_executable} -r #{conjur_cookbook_url} " \
85
+ "-o #{conjur_run_list}"] if run_chef?)
86
+ ].join "\n"
87
+ end
88
+
89
+ def self.rc configuration
90
+ [
91
+ "account: #{configuration['account']}",
92
+ "appliance_url: #{configuration['appliance_url']}",
93
+ "cert_file: /etc/conjur-#{configuration['account']}.pem",
94
+ "netrc_path: /etc/conjur.identity",
95
+ "plugins: []"
96
+ ].join "\n"
97
+ end
98
+
99
+ def self.identity configuration
100
+ """
101
+ machine #{configuration['appliance_url']}/authn
102
+ login host/#{configuration['id']}
103
+ password #{configuration['api_key']}
104
+ """
105
+ end
106
+
107
+ def configure_conjur configuration
108
+ [
109
+ write_file("/etc/conjur.conf", Script.rc(configuration)),
110
+ write_file(
111
+ "/etc/conjur-#{configuration['account']}.pem",
112
+ configuration["certificate"]
113
+ ),
114
+ write_file(
115
+ "/etc/conjur.identity",
116
+ Script.identity(configuration),
117
+ mode: 0600
118
+ )
119
+ ].join "\n"
120
+ end
121
+
122
+ def generate configuration
123
+ fail "No 'id' field in host JSON" unless configuration["id"]
124
+ fail "No 'api_key' field in host JSON" unless configuration["api_key"]
125
+
126
+ [
127
+ HEADER,
128
+ configure_conjur(configuration),
129
+ chef_script
130
+ ].join("\n")
131
+ end
132
+ end
133
+ 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.28.2"
22
+ VERSION = "4.29.0"
23
23
  ::Version=VERSION
24
24
  end
data/publish.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/bash -ex
2
+
3
+ export DEBUG=true
4
+ export GLI_DEBUG=true
5
+
6
+ debify publish 4.6 cli
@@ -17,7 +17,7 @@ describe Conjur::Command::Elevate do
17
17
 
18
18
  expect(RestClient::Request).to receive(:execute).with({
19
19
  method: :get,
20
- url: "https://core.example.com/users/alice",
20
+ url: "https://core.example.com/api/users/alice",
21
21
  username: "dknuth",
22
22
  headers: {:authorization=>"Token token=\"eyJsb2dpbiI6ImRrbnV0aCJ9\"", x_conjur_privilege: "elevate"}
23
23
  }).and_return(double(:response, body: "[]"))
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'conjur/command/host_factories'
3
+
4
+ describe Conjur::Command::HostFactories, :logged_in => true do
5
+ let (:group_memberships) { double(:group_memberships, :roleid => 'the-account:group:security_admin') }
6
+ let (:group) { double(:group, :exists? => true, :memberships => [group_memberships]) }
7
+ let (:layer_members) { double(:layer_members, :member => double(:member, :roleid => 'the-account:group:security_admin'), :admin_option => true ) }
8
+ let (:layer_role) { double(:layer_role, :members => [layer_members]) }
9
+ let (:layer) { double(:layer, :exists? => true, :role => layer_role) }
10
+
11
+ before do
12
+ allow(Conjur::Command.api).to receive(:role).with("the-account:group:the-group").and_return group
13
+ allow(Conjur::Command.api).to receive(:layer).with("layer1").and_return layer
14
+ end
15
+
16
+ describe_command 'hostfactory:create --as-group the-group --layer layer1 hf1 ' do
17
+
18
+ it 'calls api.create_host_factory and prints the results' do
19
+ expect_any_instance_of(Conjur::API).to receive(:create_host_factory).and_return '{}'
20
+ expect { invoke }.to write('{}')
21
+ end
22
+ end
23
+
24
+ context 'command-line errors' do
25
+ describe_command 'hostfactory:create hf1' do
26
+ it "fails without owner" do
27
+ expect {invoke}.to raise_error('Use --as-group or --as-role to indicate the host factory role')
28
+ end
29
+ end
30
+ describe_command 'hostfactory:create --as-group the-group hf' do
31
+ it "fails without layer" do
32
+ expect {invoke}.to raise_error('Provide at least one layer')
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -1,30 +1,94 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Conjur::Command::Hosts, logged_in: true do
4
- let(:collection_url) { "https://core.example.com/hosts" }
5
-
6
- describe_command "host:create" do
7
- it "lets the server assign the id" do
8
- expect(RestClient::Request).to receive(:execute).with({
9
- method: :post,
10
- url: collection_url,
11
- headers: {},
12
- payload: {}
13
- }).and_return(post_response('assigned-id'))
14
-
15
- expect { invoke }.to write({ id: 'assigned-id' }).to(:stdout)
4
+ let(:collection_url) { "https://core.example.com/api/hosts" }
5
+
6
+ context "creating a host" do
7
+ let(:new_host) { double("new-host") }
8
+
9
+ describe_command "host:create" do
10
+ it "lets the server assign the id" do
11
+ expect(RestClient::Request).to receive(:execute).with({
12
+ method: :post,
13
+ url: collection_url,
14
+ headers: {},
15
+ payload: {}
16
+ }).and_return(post_response('assigned-id'))
17
+
18
+ expect { invoke }.to write({ id: 'assigned-id' }).to(:stdout)
19
+ end
20
+ end
21
+ describe_command "host:create the-id" do
22
+ it "propagates the user-assigned id" do
23
+ expect(RestClient::Request).to receive(:execute).with({
24
+ method: :post,
25
+ url: collection_url,
26
+ headers: {},
27
+ payload: { id: 'the-id' }
28
+ }).and_return(post_response('the-id'))
29
+
30
+ expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
31
+ end
32
+ end
33
+ describe_command "host:create --cidr 192.168.1.1,127.0.0.0/32" do
34
+ it "Creates a host with specified CIDR" do
35
+ expect_any_instance_of(Conjur::API).to receive(:create_host).with(
36
+ { cidr: ['192.168.1.1', '127.0.0.0/32'] }
37
+ ).and_return new_host
38
+ invoke
39
+ end
40
+ end
41
+ describe_command "host:create --as-group security_admin --cidr 192.168.1.1,127.0.0.0/32" do
42
+ it "Creates a host with specified CIDR" do
43
+ expect(api).to receive(:group).with("security_admin").and_return(double(:group, roleid: "the-account:group:security_admin"))
44
+ expect(api).to receive(:role).with("the-account:group:security_admin").and_return(double(:group_role, exists?: true))
45
+ expect_any_instance_of(Conjur::API).to receive(:create_host).with(
46
+ { ownerid: "the-account:group:security_admin", cidr: ['192.168.1.1', '127.0.0.0/32'] }
47
+ ).and_return new_host
48
+ invoke
49
+ end
16
50
  end
17
51
  end
18
- describe_command "host:create the-id" do
19
- it "propagates the user-assigned id" do
20
- expect(RestClient::Request).to receive(:execute).with({
21
- method: :post,
22
- url: collection_url,
23
- headers: {},
24
- payload: { id: 'the-id' }
25
- }).and_return(post_response('the-id'))
26
-
27
- expect { invoke }.to write({ id: 'the-id' }).to(:stdout)
52
+
53
+ context "updating host attributes" do
54
+ describe_command "host update --cidr 127.0.0.0/32 the-user" do
55
+ it "updates the CIDR" do
56
+ stub_host = double()
57
+ expect_any_instance_of(Conjur::API).to receive(:host).with("the-user").and_return stub_host
58
+ expect(stub_host).to receive(:update).with(cidr: ['127.0.0.0/32']).and_return ""
59
+ expect { invoke }.to write "Host updated"
60
+ end
61
+ end
62
+
63
+ describe_command "host update --cidr all the-user" do
64
+ it "resets the CIDR restrictions" do
65
+ stub_host = double()
66
+ expect_any_instance_of(Conjur::API).to receive(:host).with("the-user").and_return stub_host
67
+ expect(stub_host).to receive(:update).with(cidr: []).and_return ""
68
+ expect { invoke }.to write "Host updated"
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'rotating api key' do
74
+ describe_command 'host rotate_api_key --host redis001' do
75
+ before do
76
+ expect(RestClient::Request).to receive(:execute).with({
77
+ method: :head,
78
+ url: 'https://core.example.com/api/hosts/redis001',
79
+ headers: {}
80
+ }).and_return true
81
+ expect(RestClient::Request).to receive(:execute).with({
82
+ method: :put,
83
+ url: 'https://authn.example.com/users/api_key?id=host%2Fredis001',
84
+ headers: {},
85
+ payload: ''
86
+ }).and_return double(:response, body: 'new api key')
87
+ end
88
+
89
+ it 'puts with basic auth' do
90
+ invoke
91
+ end
28
92
  end
29
93
  end
30
94
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Conjur::Command::Users, logged_in: true do
4
- let(:create_user_url) { "https://core.example.com/users" }
4
+ let(:create_user_url) { "https://core.example.com/api/users" }
5
5
  let(:update_password_url) { "https://authn.example.com/users/password" }
6
6
 
7
7
  context "creating a user" do
@@ -31,16 +31,41 @@ describe Conjur::Command::Users, logged_in: true do
31
31
  invoke
32
32
  end
33
33
  end
34
+ describe_command "#{cmd} --cidr 192.168.1.1,127.0.0.0/32 the-user" do
35
+ it "Creates a user with specified CIDR" do
36
+ expect_any_instance_of(Conjur::API).to receive(:create_user).with(
37
+ "the-user", { cidr: ['192.168.1.1', '127.0.0.0/32'] }
38
+ ).and_return new_user
39
+ invoke
40
+ end
41
+ end
34
42
  end
35
43
  end
36
44
 
37
- context "updating UID number" do
45
+ context "updating user attributes" do
38
46
  describe_command "user update --uidnumber 12345 the-user" do
39
47
  it "updates the uidnumber" do
40
48
  stub_user = double()
41
49
  expect_any_instance_of(Conjur::API).to receive(:user).with("the-user").and_return stub_user
42
50
  expect(stub_user).to receive(:update).with(uidnumber: 12345).and_return ""
43
- expect { invoke }.to write "UID set"
51
+ expect { invoke }.to write "User updated"
52
+ end
53
+ end
54
+ describe_command "user update --cidr 127.0.0.0/32 the-user" do
55
+ it "updates the CIDR" do
56
+ stub_user = double()
57
+ expect_any_instance_of(Conjur::API).to receive(:user).with("the-user").and_return stub_user
58
+ expect(stub_user).to receive(:update).with(cidr: ['127.0.0.0/32']).and_return ""
59
+ expect { invoke }.to write "User updated"
60
+ end
61
+ end
62
+
63
+ describe_command "user update --cidr all the-user" do
64
+ it "resets the CIDR restrictions" do
65
+ stub_user = double()
66
+ expect_any_instance_of(Conjur::API).to receive(:user).with("the-user").and_return stub_user
67
+ expect(stub_user).to receive(:update).with(cidr: []).and_return ""
68
+ expect { invoke }.to write "User updated"
44
69
  end
45
70
  end
46
71
  end
@@ -81,4 +106,27 @@ describe Conjur::Command::Users, logged_in: true do
81
106
  end
82
107
  end
83
108
  end
109
+
110
+ context 'rotating api key' do
111
+ describe_command 'user rotate_api_key' do
112
+ before do
113
+ expect(RestClient::Request).to receive(:execute).with({
114
+ method: :put,
115
+ url: 'https://authn.example.com/users/api_key',
116
+ user: username,
117
+ password: api_key,
118
+ headers: {},
119
+ payload: ''
120
+ }).and_return double(:response, body: 'new api key')
121
+ expect(Conjur::Authn).to receive(:save_credentials).with({
122
+ username: username,
123
+ password: 'new api key'
124
+ })
125
+ end
126
+
127
+ it 'puts with basic auth' do
128
+ invoke
129
+ end
130
+ end
131
+ end
84
132
  end