conjur-api 4.20.1 → 4.21.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 499e973eac8164648777759b0128f502ad692bb6
4
- data.tar.gz: 6d85fe429b8d77cb7aaca33370c0bc48179e5345
3
+ metadata.gz: d71200c2e70d47ac438ff0c37aefb9eb3336291a
4
+ data.tar.gz: 81b7029927b6227918d9449e5e279280194a56a1
5
5
  SHA512:
6
- metadata.gz: fa2d3a7024f3d41e6e87f81873179a1528b5d9b6d0a5657a8e8c281d8368189b3db301d3b525d88697d049d44868bac8df6b26aa026d2da48e785ed62d4ab843
7
- data.tar.gz: 301f2f79b84e3c8a3f13a2cdb21666d33e3cc80bc0e5144aa8974e19a248070d49c081d6ffaabc41c4b06925cbfce7b637a4b30a91b3aba36e94e12927a0c160
6
+ metadata.gz: 6f2dc2ffd7e4a1fe1b183e25c907dbb3e442e68e324993405913256ad2b1a6d44465485d30a686aa94db161ea302d0e15da84500c669b1824ad6623a04f7225b
7
+ data.tar.gz: c991fdd8ff7d36c2dbc3d74e03f469d9d235ced8b30735b3454991aa9ea53c3f56140b0981022823f2c092c72b2ec1336140956f2745ec5237ee9de0ac996dee
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ features/reports
1
2
  .DS_Store
2
3
  build_number
3
4
  *.gem
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # v4.21.0
2
+
3
+ * Add extensible Bootstrap commands as API methods.
4
+ * `bootstrap` grants `reveal` and `elevate` to the `security_admin` group.
5
+ * `bootstrap` creates `webservice:authn-tv`.
6
+ * `bootstrap` creates an `auditors` group and gives `reveal` privilege to it.
7
+
1
8
  # v4.20.1
2
9
 
3
10
  * BUGFIX: Better handling for unicode and special characters in user ids.
data/Rakefile CHANGED
@@ -1,26 +1,25 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
3
  require "yard"
4
+ require 'ci/reporter/rake/rspec'
5
+ require 'cucumber'
6
+ require 'cucumber/rake/task'
7
+ require 'rspec/core/rake_task'
4
8
 
5
- begin
6
- require 'rspec/core/rake_task'
7
- RSpec::Core::RakeTask.new(:spec) do |t|
8
- t.rspec_opts = '--order rand'
9
- end
10
- rescue LoadError
11
- $stderr.puts "RSpec Rake tasks not available in environment #{ENV['RACK_ENV']}"
12
- end
13
-
9
+ RSpec::Core::RakeTask.new :spec
10
+ Cucumber::Rake::Task.new :features
14
11
  YARD::Rake::YardocTask.new(:yard)
15
12
 
16
- task :jenkins do
13
+ task :jenkins => ['ci:setup:rspec', :spec] do
17
14
  if ENV['BUILD_NUMBER']
18
15
  File.write('build_number', ENV['BUILD_NUMBER'])
19
16
  end
20
- require 'ci/reporter/rake/rspec'
21
- Rake::Task["ci:setup:rspec"].invoke
22
- Rake::Task["spec"].invoke
17
+ require 'fileutils'
18
+ FileUtils.rm_rf 'features/reports'
19
+ Cucumber::Rake::Task.new do |t|
20
+ t.cucumber_opts = "--tags ~@real-api --format pretty --format junit --out features/reports"
21
+ end.runner.run
23
22
  Rake::Task["yard"].invoke
24
23
  end
25
24
 
26
- task default: :spec
25
+ task default: [:spec, :features]
data/ci/test.sh ADDED
@@ -0,0 +1,9 @@
1
+ #!/bin/bash -e
2
+
3
+ cd /src/conjur-api
4
+
5
+ export CONJUR_AUTHN_LOGIN=admin
6
+ export CONJUR_AUTHN_API_KEY=secret
7
+
8
+ bundle
9
+ bundle exec rake jenkins || true
data/conjur-api.gemspec CHANGED
@@ -28,6 +28,9 @@ Gem::Specification.new do |gem|
28
28
  gem.add_development_dependency 'rspec', '~> 3'
29
29
  gem.add_development_dependency 'rspec-expectations', '~> 3.4'
30
30
  gem.add_development_dependency 'webmock'
31
+ gem.add_development_dependency 'cucumber'
32
+ gem.add_development_dependency 'conjur-cli'
33
+ gem.add_development_dependency 'conjur-debify'
31
34
  gem.add_development_dependency 'ci_reporter_rspec'
32
35
  gem.add_development_dependency 'simplecov'
33
36
  gem.add_development_dependency 'io-grab'
@@ -0,0 +1,27 @@
1
+ Feature: conjur bootstrap
2
+
3
+ Background: Bootstrap
4
+ Given I bootstrap
5
+
6
+ Scenario: Expected resources exist
7
+ Then expressions "$conjur.group('security_admin').exists?" and "true" are equal
8
+ Then expressions "$conjur.group('auditors').exists?" and "true" are equal
9
+ Then expressions "$conjur.group('pubkeys-1.0/key-managers').exists?" and "true" are equal
10
+ Then expressions "$conjur.resource('webservice:conjur/authn-tv').exists?" and "true" are equal
11
+ Then expressions "$conjur.resource('webservice:conjur/policy-loader').exists?" and "true" are equal
12
+ Then expressions "$conjur.host('conjur/policy-loader').exists?" and "true" are equal
13
+ Then expressions "$conjur.host('conjur/secrets-rotator').exists?" and "true" are equal
14
+ Then expressions "$conjur.host('conjur/ldap-sync').exists?" and "true" are equal
15
+
16
+ Scenario: security_admin group has the expected members
17
+ Then expressions "$conjur.role('group:security_admin').members.map(&:member).map(&:roleid).sort.join(',')" and "'cucumber:host:conjur/authn-tv,cucumber:host:conjur/ldap-sync,cucumber:host:conjur/policy-loader,cucumber:host:conjur/secrets-rotator,cucumber:user:admin'" are equal
18
+
19
+ Scenario: security_admin can 'elevate' and 'reveal'
20
+ Then expression "$conjur.resource('!:!:conjur').permitted_roles('elevate')" includes "$conjur.group('security_admin').roleid"
21
+ Then expression "$conjur.resource('!:!:conjur').permitted_roles('reveal')" includes "$conjur.group('security_admin').roleid"
22
+
23
+ Scenario: auditors can 'reveal'
24
+ Then expression "$conjur.resource('!:!:conjur').permitted_roles('reveal')" includes "$conjur.group('auditors').roleid"
25
+
26
+ Scenario: API keys are saved in variables
27
+ Then expression "$conjur.resources(kind: 'variable').map(&:resourceid)" includes "'cucumber:variable:conjur/hosts/conjur/secrets-rotator/api-key'"
@@ -0,0 +1,24 @@
1
+ Then(/^I bootstrap$/) do
2
+ class Listener
3
+ attr_accessor :messages
4
+
5
+ def initialize
6
+ @messages = []
7
+ end
8
+
9
+ def echo msg
10
+ @messages.push msg
11
+ end
12
+ end
13
+ @listener = Listener.new
14
+
15
+ $conjur.bootstrap @listener
16
+ end
17
+
18
+ Then(/^expressions "([^"]*)" and "([^"]*)" are equal$/) do |code, test|
19
+ expect(eval(code)).to eq(eval(test))
20
+ end
21
+
22
+ Then(/^expression "([^"]*)" includes "([^"]*)"$/) do |code, test|
23
+ expect(eval(code)).to include(eval(test))
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'conjur/cli'
2
+
3
+ Conjur::Config.load
4
+ Conjur::Config.apply
5
+ $conjur = Conjur::Authn.connect nil, noask: true
data/jenkins.sh CHANGED
@@ -1,11 +1,27 @@
1
- #!/bin/bash -e
1
+ #!/bin/bash -ex
2
2
 
3
- docker build -t api-ruby .
3
+ CONJUR_VERSION=${CONJUR_VERSION:-"4.6"}
4
+ DOCKER_IMAGE=${DOCKER_IMAGE:-"registry.tld/conjur-appliance-cuke-master:$CONJUR_VERSION-stable"}
5
+ NOKILL=${NOKILL:-"0"}
6
+ PULL=${PULL:-"1"}
4
7
 
5
- docker run --rm \
6
- -v $PWD:/src \
7
- api-ruby \
8
- bash -c '''
9
- bundle
10
- bundle exec rake jenkins
11
- '''
8
+ if [ -z "$CONJUR_CONTAINER" ]; then
9
+ if [ "$PULL" == "1" ]; then
10
+ docker pull $DOCKER_IMAGE
11
+ fi
12
+
13
+ cid=$(docker run -d -v ${PWD}:/src/conjur-api $DOCKER_IMAGE)
14
+ function finish {
15
+ if [ "$NOKILL" != "1" ]; then
16
+ docker rm -f ${cid}
17
+ fi
18
+ }
19
+ trap finish EXIT
20
+
21
+ >&2 echo "Container id:"
22
+ >&2 echo $cid
23
+ else
24
+ cid=${CONJUR_CONTAINER}
25
+ fi
26
+
27
+ docker exec -i ${cid} /src/conjur-api/ci/test.sh
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Conjur
21
21
  class API
22
- VERSION = "4.20.1"
22
+ VERSION = "4.21.0"
23
23
  end
24
24
  end
data/lib/conjur/api.rb CHANGED
@@ -42,6 +42,7 @@ require 'conjur/core-api'
42
42
  require 'conjur/layer-api'
43
43
  require 'conjur/pubkeys-api'
44
44
  require 'conjur/host-factory-api'
45
+ require 'conjur/bootstrap'
45
46
  require 'conjur-api/version'
46
47
  require 'conjur/api/info'
47
48
 
@@ -56,7 +56,7 @@ module Conjur
56
56
  log do |logger|
57
57
  logger << "Creating host_factory #{id}"
58
58
  unless options.blank?
59
- logger << " with options #{options.inspect}"
59
+ logger << " with options #{options.to_json}"
60
60
  end
61
61
  end
62
62
  options ||= {}
@@ -25,7 +25,7 @@ module Conjur
25
25
  class API
26
26
  class << self
27
27
  # @api private
28
- # TODO WTF does this do!
28
+ # deprecated
29
29
  def enroll_host(url)
30
30
  if Conjur.log
31
31
  Conjur.log << "Enrolling host with URL #{url}\n"
data/lib/conjur/base.rb CHANGED
@@ -186,6 +186,15 @@ module Conjur
186
186
  def username
187
187
  @username || @token['data']
188
188
  end
189
+
190
+ # Perform all commands in Conjur::Bootstrap::Command.
191
+ def bootstrap listener
192
+ Conjur::Bootstrap::Command.constants.map{|c| Conjur::Bootstrap::Command.const_get(c)}.each do |cls|
193
+ next unless cls.is_a?(Class)
194
+ next unless cls.superclass == Conjur::Bootstrap::Command::Base
195
+ cls.new(self, listener).perform
196
+ end
197
+ end
189
198
 
190
199
  # @api private
191
200
  # used to delegate to host providing subclasses.
@@ -0,0 +1,151 @@
1
+ module Conjur
2
+ module Bootstrap
3
+ module Command
4
+ Base = Struct.new(:api, :listener) do
5
+ def echo msg
6
+ listener.echo msg
7
+ end
8
+
9
+ def security_admin
10
+ api.group("security_admin")
11
+ end
12
+
13
+ def auditors
14
+ api.group("auditors")
15
+ end
16
+
17
+ def find_or_create_record record, owner = nil, &block
18
+ if record.exists?
19
+ echo "#{record.resource_kind.capitalize} '#{record.id}' already exists"
20
+ record
21
+ else
22
+ echo "Creating #{record.resource_kind} '#{record.id}'"
23
+ options = {}
24
+ options[:ownerid] = owner.roleid if owner
25
+ result = if block_given?
26
+ yield record, options
27
+ else
28
+ api.send "create_#{record.resource_kind}", record.id, options
29
+ end
30
+ store_api_key result if result.attributes['api_key']
31
+ result
32
+ end
33
+ end
34
+
35
+ def find_or_create_resource resource, owner = nil
36
+ if resource.exists?
37
+ echo "#{resource.resource_kind.capitalize} '#{resource.identifier}' already exists"
38
+ else
39
+ echo "Creating #{resource.resource_kind} '#{resource.identifier}'"
40
+ options = {}
41
+ options[:ownerid] = owner.roleid if owner
42
+ api.create_resource resource.resourceid, options
43
+ end
44
+ end
45
+
46
+ def store_api_key user
47
+ api.create_variable "text/plain",
48
+ "conjur-api-key",
49
+ id: "conjur/#{user.resource_kind.pluralize}/#{user.id}/api-key",
50
+ value: user.api_key,
51
+ ownerid: security_admin.role.roleid
52
+ echo "The API of #{user.resource_kind} #{user.id} is stored in variable 'conjur/#{user.resource_kind.pluralize}/#{user.id}/api-key'. " +
53
+ "You can retire the variable if you don't want to keep it there."
54
+ end
55
+
56
+ def permit resource, privilege, role
57
+ if resource.permitted_roles(privilege).member?(role.roleid)
58
+ echo "#{role.roleid} already has '#{privilege}' privilege on #{resource.resourceid}"
59
+ else
60
+ resource.permit privilege, role
61
+ end
62
+ end
63
+ end
64
+
65
+ class SecurityAdminGroup < Base
66
+ def perform
67
+ find_or_create_record security_admin
68
+
69
+ security_admin.resource.give_to(security_admin) unless security_admin.resource.ownerid == security_admin.role.roleid
70
+ end
71
+ end
72
+
73
+ class AuditorsGroup < Base
74
+ def perform
75
+ find_or_create_record auditors, security_admin
76
+ end
77
+ end
78
+
79
+ class Pubkeys < Base
80
+ def perform
81
+ find_or_create_record key_managers, security_admin
82
+ find_or_create_record pubkeys_layer, security_admin
83
+ find_or_create_record pubkeys_host, security_admin do |record, options|
84
+ api.create_host(id: record.id, ownerid: security_admin.roleid)
85
+ end
86
+ pubkeys_layer.add_host pubkeys_host unless pubkeys_layer.hosts.map(&:roleid).member?(pubkeys_host.roleid)
87
+
88
+ find_or_create_resource pubkeys_service, security_admin
89
+ permit pubkeys_service, 'update', key_managers
90
+ end
91
+
92
+ def pubkeys_layer
93
+ api.layer("pubkeys-1.0/public-keys")
94
+ end
95
+
96
+ def pubkeys_host
97
+ api.host("conjur/pubkeys")
98
+ end
99
+
100
+ def pubkeys_service
101
+ api.resource("service:pubkeys-1.0/public-keys")
102
+ end
103
+
104
+ def key_managers
105
+ api.group("pubkeys-1.0/key-managers")
106
+ end
107
+ end
108
+
109
+ class Attic < Base
110
+ def perform
111
+ find_or_create_record attic
112
+ end
113
+
114
+ def attic_user_name
115
+ "attic"
116
+ end
117
+
118
+ def attic
119
+ api.user(attic_user_name)
120
+ end
121
+ end
122
+
123
+ # Create a set of hosts that have security_admin privilege.
124
+ class SystemAccounts < Base
125
+ def perform
126
+ for hostname in %w(conjur/authn-tv conjur/secrets-rotator conjur/policy-loader conjur/ldap-sync)
127
+ find_or_create_resource api.resource("webservice:#{hostname}"), security_admin
128
+ find_or_create_record api.host(hostname), security_admin do |record, options|
129
+ api.create_host(id: record.id, ownerid: security_admin.roleid).tap do |host|
130
+ host.role.revoke_from security_admin
131
+ security_admin.add_member host
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ class GlobalPrivileges < Base
139
+ def perform
140
+ permit conjur_resource, 'elevate', security_admin
141
+ permit conjur_resource, 'reveal', security_admin
142
+ permit conjur_resource, 'reveal', auditors
143
+ end
144
+
145
+ def conjur_resource
146
+ api.resource("!:!:conjur")
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
data/lib/conjur/layer.rb CHANGED
@@ -77,7 +77,7 @@ module Conjur
77
77
  # @return [Array<Conjur::Host>] the hosts in the layer.
78
78
  def hosts
79
79
  self.attributes['hosts'].collect do |id|
80
- Conjur::Host.new(Conjur::API.core_asset_host, options)["hosts/#{fully_escape id}"]
80
+ Conjur::Host.new(Conjur::API.core_asset_host, options)["hosts/#{fully_escape id.split(':', 3)[-1]}"]
81
81
  end
82
82
  end
83
83
  end
@@ -34,6 +34,8 @@ module Conjur
34
34
  include PathBased
35
35
  include Exists
36
36
 
37
+ alias resource_kind kind
38
+
37
39
  # The identifier part of the `resource_id` for this resource. The identifier
38
40
  # is the resource id without the `account` and `kind` parts.
39
41
  #
@@ -45,7 +45,7 @@ module Conjur
45
45
  logger << "Creating #{type}"
46
46
  logger << " #{id}" if id
47
47
  unless options.blank?
48
- logger << " with options #{options.inspect}"
48
+ logger << " with options #{options.to_json}"
49
49
  end
50
50
  end
51
51
  options ||= {}
data/spec/ssl_spec.rb CHANGED
@@ -25,18 +25,20 @@ describe 'SSL connection' do
25
25
  expect { Conjur::API.login 'foo', 'bar' }.to raise_error RestClient::ResourceNotFound
26
26
  end
27
27
  end
28
-
29
- let(:port) { 54_128 }
28
+
29
+ let(:server) do
30
+ server = WEBrick::HTTPServer.new \
31
+ Port: 0, SSLEnable: true,
32
+ AccessLog: [], Logger: Logger.new('/dev/null'), # shut up, WEBrick
33
+ SSLCertificate: cert, SSLPrivateKey: key
34
+ end
35
+ let(:port) { server.config[:Port] }
30
36
 
31
37
  before do
32
38
  allow(Conjur::Authn::API).to receive(:host).and_return "https://localhost:#{port}"
33
39
  end
34
40
 
35
41
  around do |example|
36
- server = WEBrick::HTTPServer.new \
37
- Port: port, SSLEnable: true,
38
- AccessLog: [], Logger: Logger.new('/dev/null'), # shut up, WEBrick
39
- SSLCertificate: cert, SSLPrivateKey: key
40
42
  server_thread = Thread.new do
41
43
  server.start
42
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.20.1
4
+ version: 4.21.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: 2016-02-18 00:00:00.000000000 Z
12
+ date: 2016-03-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
@@ -129,6 +129,48 @@ dependencies:
129
129
  - - '>='
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
+ - !ruby/object:Gem::Dependency
133
+ name: cucumber
134
+ requirement: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ type: :development
140
+ prerelease: false
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ - !ruby/object:Gem::Dependency
147
+ name: conjur-cli
148
+ requirement: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ type: :development
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ - !ruby/object:Gem::Dependency
161
+ name: conjur-debify
162
+ requirement: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
132
174
  - !ruby/object:Gem::Dependency
133
175
  name: ci_reporter_rspec
134
176
  requirement: !ruby/object:Gem::Requirement
@@ -279,11 +321,11 @@ files:
279
321
  - LICENSE
280
322
  - README.md
281
323
  - Rakefile
324
+ - ci/test.sh
282
325
  - conjur-api.gemspec
283
- - features/enroll_server.feature
284
- - features/login.feature
285
- - features/ping_as_server.feature
286
- - features/ping_as_user.feature
326
+ - features/bootstrap.feature
327
+ - features/step_definitions/api_steps.rb
328
+ - features/support/env.rb
287
329
  - jenkins.sh
288
330
  - lib/conjur-api.rb
289
331
  - lib/conjur-api/version.rb
@@ -311,6 +353,7 @@ files:
311
353
  - lib/conjur/authn-api.rb
312
354
  - lib/conjur/authz-api.rb
313
355
  - lib/conjur/base.rb
356
+ - lib/conjur/bootstrap.rb
314
357
  - lib/conjur/build_from_response.rb
315
358
  - lib/conjur/cast.rb
316
359
  - lib/conjur/cert_utils.rb
@@ -416,10 +459,9 @@ signing_key:
416
459
  specification_version: 4
417
460
  summary: Conjur API
418
461
  test_files:
419
- - features/enroll_server.feature
420
- - features/login.feature
421
- - features/ping_as_server.feature
422
- - features/ping_as_user.feature
462
+ - features/bootstrap.feature
463
+ - features/step_definitions/api_steps.rb
464
+ - features/support/env.rb
423
465
  - spec/api/authn_spec.rb
424
466
  - spec/api/graph_spec.rb
425
467
  - spec/api/groups_spec.rb
@@ -1,26 +0,0 @@
1
- Feature: Server enrollment
2
-
3
- Background:
4
- When I login with my username and password
5
- And I receive an API key
6
-
7
- Scenario: Enroll this server
8
- When I enroll a server
9
- Then the request succeeds
10
- Then I receive an enrollment key
11
-
12
- Scenario: The server is granted no other roles by default
13
- Given I enroll a server
14
- And I switch to the server role
15
- When I list my roles
16
- Then the result contains 1 item
17
-
18
- Scenario: The server can be granted other roles
19
- Given I enroll a server
20
- And I create a role
21
- And I grant the role to the server
22
- And I switch to the server role
23
- And I list my roles
24
- Then the result contains 2 items
25
-
26
-
@@ -1,13 +0,0 @@
1
- Feature: Login
2
-
3
- Scenario: Login with my username and password
4
- When I login with my username and password
5
- Then the request succeeds
6
- And I receive an API key
7
-
8
- Scenario: Login with my username and password
9
- When I login with my username and invalid password
10
- Then the request fails with error 401
11
-
12
-
13
-
@@ -1,16 +0,0 @@
1
- Feature: Ping
2
-
3
- Background:
4
- Given I login with my username and password
5
- And I receive an API key
6
- And I enroll a server
7
- And I switch to the server role
8
-
9
- Scenario: Ping using the server enrollment key
10
- When I ping
11
- Then the request succeeds
12
-
13
- Scenario: Server credentials do not work from a different host
14
- Given I force the request IP address to 127.0.0.1
15
- When I ping
16
- Then the request fails with error 401
@@ -1,9 +0,0 @@
1
- Feature: Ping
2
-
3
- Background:
4
- When I login with my username and password
5
- And I receive an API key
6
-
7
- Scenario: Ping using an API key
8
- When I ping
9
- Then the request succeeds