conjur-api 6.3.0 → 6.3.1.pre.819
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 +12 -0
- data/VERSION +1 -1
- data/features/load_policy.feature +37 -0
- data/features/step_definitions/result_steps.rb +4 -0
- data/lib/conjur/api/policies.rb +37 -0
- data/lib/conjur/api/router.rb +21 -0
- data/lib/conjur/api/server_version.rb +50 -0
- data/lib/conjur/api/version_check.rb +34 -0
- data/lib/conjur/api.rb +2 -0
- data/lib/conjur/saas.rb +39 -0
- data/spec/api/policies_spec.rb +118 -0
- data/spec/api/router_spec.rb +43 -0
- data/spec/api/server_version_spec.rb +111 -0
- data/spec/api/version_check_spec.rb +40 -0
- data/spec/unit/saas_spec.rb +40 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 530b8d1955af810bdfc511cf8b2cde690dfb6a4afc4a7a2505b86a2aa92ece1d
|
|
4
|
+
data.tar.gz: 6f3ce0a8e7780eef3cfd8d55d174fadcd06593539cae63146bac3ec796cbe510
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 704610d1d7046bbf65619078c5273b1c4680969fa81659575d976fe42b1cc14f4d23d8b0933b0daeb09549b466df13414b166e96860e0fd74bd8a0dac20d906e
|
|
7
|
+
data.tar.gz: 7391aa9ccdd2f31b38180c8a5d6c78cd7958519af25fae415682e91ecab8d5834d02bd62931cd7fa4728b5665b19ca265f8e1ef88b762a1dbd64b8c983480391
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## [6.3.1] - 2026-07-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Added `Conjur::API#dry_run_policy` for validating a policy load without applying it,
|
|
13
|
+
reporting what would be created/updated/deleted or, for invalid YAML, the parse errors.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `Conjur::API#dry_run_policy` and `Conjur::API#fetch_policy` now raise
|
|
17
|
+
`Conjur::FeatureNotAvailable` when called against CyberArk Secrets Manager, SaaS, or
|
|
18
|
+
against a Conjur server older than 1.21.1, matching conjur-api-go's behavior for the
|
|
19
|
+
equivalent methods.
|
|
20
|
+
|
|
9
21
|
## [6.3.0] - 2026-07-15
|
|
10
22
|
|
|
11
23
|
### Added
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.3.
|
|
1
|
+
6.3.1-819
|
|
@@ -92,3 +92,40 @@ Feature: Load a policy.
|
|
|
92
92
|
$conjur.fetch_policy 'fetch-policy-json', return_json: true
|
|
93
93
|
"""
|
|
94
94
|
Then the result should contain "fetch-policy-json-group"
|
|
95
|
+
|
|
96
|
+
Scenario: A dry run reports what a policy load would do without applying it.
|
|
97
|
+
Given I run the code:
|
|
98
|
+
"""
|
|
99
|
+
$conjur.load_policy 'root', <<-POLICY
|
|
100
|
+
- !policy
|
|
101
|
+
id: dry-run-policy
|
|
102
|
+
body: []
|
|
103
|
+
POLICY
|
|
104
|
+
"""
|
|
105
|
+
Then I can run the code:
|
|
106
|
+
"""
|
|
107
|
+
$conjur.dry_run_policy 'dry-run-policy', <<-POLICY
|
|
108
|
+
- !group dry-run-group
|
|
109
|
+
POLICY
|
|
110
|
+
"""
|
|
111
|
+
Then the JSON should have "status"
|
|
112
|
+
Then I can run the code:
|
|
113
|
+
"""
|
|
114
|
+
$conjur.fetch_policy 'dry-run-policy'
|
|
115
|
+
"""
|
|
116
|
+
Then the result should not contain "dry-run-group"
|
|
117
|
+
|
|
118
|
+
Scenario: A dry run reports errors for invalid policy YAML without applying it.
|
|
119
|
+
Given I run the code:
|
|
120
|
+
"""
|
|
121
|
+
$conjur.load_policy 'root', <<-POLICY
|
|
122
|
+
- !policy
|
|
123
|
+
id: dry-run-invalid-policy
|
|
124
|
+
body: []
|
|
125
|
+
POLICY
|
|
126
|
+
"""
|
|
127
|
+
Then I can run the code:
|
|
128
|
+
"""
|
|
129
|
+
$conjur.dry_run_policy 'dry-run-invalid-policy', "- !group [invalid"
|
|
130
|
+
"""
|
|
131
|
+
Then the JSON should have "errors"
|
|
@@ -6,6 +6,10 @@ Then(/^the result should contain "([^"]+)"$/) do |expected|
|
|
|
6
6
|
expect(@result.to_s).to include(expected)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
Then(/^the result should not contain "([^"]+)"$/) do |expected|
|
|
10
|
+
expect(@result.to_s).not_to include(expected)
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
Then(/^the providers list contains service id "([^"]+)"$/) do |service_id|
|
|
10
14
|
expect(@result.map{ |x| x["service_id"]}).to include(service_id)
|
|
11
15
|
end
|
data/lib/conjur/api/policies.rb
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
#
|
|
21
21
|
require 'conjur/policy_load_result'
|
|
22
22
|
require 'conjur/policy'
|
|
23
|
+
require 'conjur/saas'
|
|
23
24
|
|
|
24
25
|
module Conjur
|
|
25
26
|
class API
|
|
@@ -51,6 +52,29 @@ module Conjur
|
|
|
51
52
|
PolicyLoadResult.new JSON.parse(request.send(method, policy))
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# Validate a policy load without applying it to the server, reporting what
|
|
56
|
+
# would change.
|
|
57
|
+
#
|
|
58
|
+
# @param id [String] id of the policy to load.
|
|
59
|
+
# @param policy [String] YAML-formatted policy definition.
|
|
60
|
+
# @param account [String] Conjur organization account
|
|
61
|
+
# @param method [Symbol] Policy load method to use: {POLICY_METHOD_POST} (default), {POLICY_METHOD_PATCH}, or {POLICY_METHOD_PUT}.
|
|
62
|
+
# @return [Hash] the dry run result, with keys such as "status", "created", "updated",
|
|
63
|
+
# "deleted", and "errors". Invalid policy YAML is reported in the result rather than
|
|
64
|
+
# raised, with "status" set to "Invalid YAML" and details in "errors". This applies
|
|
65
|
+
# only to the 422 response the server uses for invalid YAML; other error responses
|
|
66
|
+
# (e.g. 403 for insufficient privileges, 400 for bad request params) have a different
|
|
67
|
+
# body shape and are raised as the corresponding RestClient exception.
|
|
68
|
+
# @raise [Conjur::FeatureNotAvailable] if the appliance is CyberArk Secrets Manager, SaaS,
|
|
69
|
+
# or the Conjur server is older than 1.21.1.
|
|
70
|
+
def dry_run_policy id, policy, account: Conjur.configuration.account, method: POLICY_METHOD_POST
|
|
71
|
+
verify_policy_dry_run_support!
|
|
72
|
+
request = url_for(:policies_dry_run_policy, credentials, account, id)
|
|
73
|
+
JSON.parse(request.send(method, policy))
|
|
74
|
+
rescue RestClient::UnprocessableEntity => e
|
|
75
|
+
JSON.parse(e.response.body)
|
|
76
|
+
end
|
|
77
|
+
|
|
54
78
|
# Fetch the current policy data from the server.
|
|
55
79
|
#
|
|
56
80
|
# @param id [String] id of the policy to fetch.
|
|
@@ -59,7 +83,10 @@ module Conjur
|
|
|
59
83
|
# @param depth [Integer, nil] Maximum depth of the returned policy tree (nil for the full tree).
|
|
60
84
|
# @param limit [Integer, nil] Maximum number of policy objects to return (nil for no limit).
|
|
61
85
|
# @return [String] the policy document, formatted as YAML or JSON.
|
|
86
|
+
# @raise [Conjur::FeatureNotAvailable] if the appliance is CyberArk Secrets Manager, SaaS,
|
|
87
|
+
# or the Conjur server is older than 1.21.1.
|
|
62
88
|
def fetch_policy id, account: Conjur.configuration.account, return_json: false, depth: nil, limit: nil
|
|
89
|
+
verify_policy_dry_run_support!
|
|
63
90
|
options = {}
|
|
64
91
|
options[:depth] = depth if depth
|
|
65
92
|
options[:limit] = limit if limit
|
|
@@ -69,5 +96,15 @@ module Conjur
|
|
|
69
96
|
end
|
|
70
97
|
|
|
71
98
|
#@!endgroup
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def verify_policy_dry_run_support!
|
|
103
|
+
if Conjur::Saas.appliance_url?(Conjur.configuration.appliance_url)
|
|
104
|
+
raise Conjur::FeatureNotAvailable, "Policy dry run and fetch are not supported in CyberArk Secrets Manager, SaaS"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
verify_min_server_version!('1.21.1')
|
|
108
|
+
end
|
|
72
109
|
end
|
|
73
110
|
end
|
data/lib/conjur/api/router.rb
CHANGED
|
@@ -148,6 +148,13 @@ module Conjur
|
|
|
148
148
|
)['policies'][fully_escape account]['policy'][fully_escape id]
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
def policies_dry_run_policy credentials, account, id
|
|
152
|
+
RestClient::Resource.new(
|
|
153
|
+
Conjur.configuration.core_url,
|
|
154
|
+
Conjur.configuration.create_rest_client_options(credentials)
|
|
155
|
+
)['policies'][fully_escape account]['policy'][fully_escape id]['?dryRun=true']
|
|
156
|
+
end
|
|
157
|
+
|
|
151
158
|
def policies_fetch_policy credentials, account, id, options = {}
|
|
152
159
|
RestClient::Resource.new(
|
|
153
160
|
Conjur.configuration.core_url,
|
|
@@ -275,6 +282,20 @@ module Conjur
|
|
|
275
282
|
)['whoami']
|
|
276
283
|
end
|
|
277
284
|
|
|
285
|
+
def server_info
|
|
286
|
+
RestClient::Resource.new(
|
|
287
|
+
Conjur.configuration.core_url,
|
|
288
|
+
Conjur.configuration.rest_client_options
|
|
289
|
+
)['info']
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def server_root
|
|
293
|
+
RestClient::Resource.new(
|
|
294
|
+
Conjur.configuration.core_url,
|
|
295
|
+
Conjur.configuration.rest_client_options
|
|
296
|
+
)['/']
|
|
297
|
+
end
|
|
298
|
+
|
|
278
299
|
private
|
|
279
300
|
|
|
280
301
|
def resource_annotations resource
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'conjur/api/router'
|
|
3
|
+
|
|
4
|
+
module Conjur
|
|
5
|
+
class API
|
|
6
|
+
#@!group Server version
|
|
7
|
+
|
|
8
|
+
# Retrieve the Conjur server version.
|
|
9
|
+
#
|
|
10
|
+
# Queries the '/info' endpoint (available on Conjur Enterprise), falling back to
|
|
11
|
+
# the root endpoint (available on Conjur OSS, and as a legacy fallback on
|
|
12
|
+
# Enterprise) if '/info' is unavailable or its response can't be parsed. The
|
|
13
|
+
# result is memoized on this instance.
|
|
14
|
+
#
|
|
15
|
+
# @return [String] the server version, e.g. "1.21.1.1-25".
|
|
16
|
+
# @raise [Conjur::FeatureNotAvailable] if the version can't be determined from
|
|
17
|
+
# either endpoint.
|
|
18
|
+
def server_version
|
|
19
|
+
return @server_version if @server_version
|
|
20
|
+
|
|
21
|
+
@server_version = version_from_info || version_from_root ||
|
|
22
|
+
raise(Conjur::FeatureNotAvailable, "Unable to determine Conjur server version")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#@!endgroup
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def version_from_info
|
|
30
|
+
body = JSON.parse(Router.server_info.get.body)
|
|
31
|
+
body.dig('services', 'possum', 'version')
|
|
32
|
+
rescue RestClient::Exception, JSON::ParserError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def version_from_root
|
|
37
|
+
response = Router.server_root.get
|
|
38
|
+
content_type = response.headers[:content_type].to_s
|
|
39
|
+
|
|
40
|
+
if content_type.include?('application/json')
|
|
41
|
+
JSON.parse(response.body)['version']
|
|
42
|
+
else
|
|
43
|
+
match = response.body.match(%r{<d[dt]>\s*Version\s*(?:</d[dt]>\s*<dd>\s*)?([^\s<]+)\s*</dd>})
|
|
44
|
+
match && match[1]
|
|
45
|
+
end
|
|
46
|
+
rescue RestClient::Exception, JSON::ParserError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Conjur
|
|
2
|
+
class API
|
|
3
|
+
#@!group Server version
|
|
4
|
+
|
|
5
|
+
# Verify that the Conjur server version is at least `min_version`.
|
|
6
|
+
#
|
|
7
|
+
# Conjur versions carry a build suffix after a hyphen (e.g. "1.21.1.1-25")
|
|
8
|
+
# which is not part of the comparable version, so it's stripped before comparing.
|
|
9
|
+
#
|
|
10
|
+
# @param min_version [String] the minimum required version, e.g. "1.21.1".
|
|
11
|
+
# @return [true] if the server version is sufficient.
|
|
12
|
+
# @raise [Conjur::FeatureNotAvailable] if the server version is unparseable or
|
|
13
|
+
# lower than `min_version`.
|
|
14
|
+
def verify_min_server_version!(min_version)
|
|
15
|
+
detected = server_version.split('-').first
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
detected_version = Gem::Version.new(detected)
|
|
19
|
+
required_version = Gem::Version.new(min_version)
|
|
20
|
+
rescue ArgumentError
|
|
21
|
+
raise Conjur::FeatureNotAvailable, "Unable to parse Conjur server version #{server_version.inspect}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if detected_version < required_version
|
|
25
|
+
raise Conjur::FeatureNotAvailable,
|
|
26
|
+
"Conjur server version #{server_version} is less than the minimum required version #{min_version}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#@!endgroup
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/conjur/api.rb
CHANGED
|
@@ -42,6 +42,8 @@ require 'conjur/api/resources'
|
|
|
42
42
|
require 'conjur/api/pubkeys'
|
|
43
43
|
require 'conjur/api/variables'
|
|
44
44
|
require 'conjur/api/policies'
|
|
45
|
+
require 'conjur/api/server_version'
|
|
46
|
+
require 'conjur/api/version_check'
|
|
45
47
|
require 'conjur/api/host_factories'
|
|
46
48
|
require 'conjur/api/ldap_sync'
|
|
47
49
|
require 'conjur/host'
|
data/lib/conjur/saas.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module Conjur
|
|
4
|
+
# Detects whether a Conjur appliance URL points at CyberArk Secrets Manager, SaaS.
|
|
5
|
+
# SaaS appliances do not expose the '/info' or root version endpoints used to
|
|
6
|
+
# determine server version, so features that depend on those endpoints must be
|
|
7
|
+
# rejected outright for SaaS rather than attempting a version check.
|
|
8
|
+
module Saas
|
|
9
|
+
# Hostname suffixes used by CyberArk Secrets Manager, SaaS appliances.
|
|
10
|
+
SUFFIXES = %w[
|
|
11
|
+
.cyberark.cloud
|
|
12
|
+
.integration-cyberark.cloud
|
|
13
|
+
.test-cyberark.cloud
|
|
14
|
+
.dev-cyberark.cloud
|
|
15
|
+
.cyberark-everest-integdev.cloud
|
|
16
|
+
.cyberark-everest-pre-prod.cloud
|
|
17
|
+
.sandbox-cyberark.cloud
|
|
18
|
+
.pt-cyberark.cloud
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
HOSTNAME_PATTERN = /(\.secretsmgr|-secretsmanager)(#{SUFFIXES.map { |s| Regexp.escape(s) }.join('|')})\z/.freeze
|
|
22
|
+
|
|
23
|
+
# Returns true if `url` is a CyberArk Secrets Manager, SaaS appliance URL.
|
|
24
|
+
#
|
|
25
|
+
# @param url [String, nil] the appliance URL to check.
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def self.appliance_url?(url)
|
|
28
|
+
return false unless url
|
|
29
|
+
|
|
30
|
+
uri = URI.parse(url)
|
|
31
|
+
return false unless uri.scheme == 'https'
|
|
32
|
+
return false unless uri.host
|
|
33
|
+
|
|
34
|
+
!!(uri.host.downcase =~ HOSTNAME_PATTERN)
|
|
35
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/spec/api/policies_spec.rb
CHANGED
|
@@ -10,7 +10,23 @@ describe "Conjur::API#fetch_policy", api: :dummy do
|
|
|
10
10
|
allow(api).to receive(:credentials).and_return('the-credentials')
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
it "raises FeatureNotAvailable for a SaaS appliance" do
|
|
14
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://x.secretsmgr.cyberark.cloud')
|
|
15
|
+
|
|
16
|
+
expect { api.fetch_policy('root', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /SaaS/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "raises FeatureNotAvailable for a server older than 1.21.1" do
|
|
20
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
21
|
+
allow(api).to receive(:server_version).and_return('1.20.0')
|
|
22
|
+
|
|
23
|
+
expect { api.fetch_policy('root', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /1\.21\.1/)
|
|
24
|
+
end
|
|
25
|
+
|
|
13
26
|
it "fetches the policy as YAML by default" do
|
|
27
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
28
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
29
|
+
|
|
14
30
|
resource = instance_double(RestClient::Resource, "policy resource")
|
|
15
31
|
allow(api).to receive(:url_for)
|
|
16
32
|
.with(:policies_fetch_policy, 'the-credentials', 'the-account', 'root', {})
|
|
@@ -24,6 +40,9 @@ describe "Conjur::API#fetch_policy", api: :dummy do
|
|
|
24
40
|
end
|
|
25
41
|
|
|
26
42
|
it "fetches the policy as JSON when requested" do
|
|
43
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
44
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
45
|
+
|
|
27
46
|
resource = instance_double(RestClient::Resource, "policy resource")
|
|
28
47
|
allow(api).to receive(:url_for)
|
|
29
48
|
.with(:policies_fetch_policy, 'the-credentials', 'the-account', 'root', { depth: 2, limit: 100 })
|
|
@@ -38,3 +57,102 @@ describe "Conjur::API#fetch_policy", api: :dummy do
|
|
|
38
57
|
).to eq('[{"id":"cucumber:host:foo"}]')
|
|
39
58
|
end
|
|
40
59
|
end
|
|
60
|
+
|
|
61
|
+
describe "Conjur::API#dry_run_policy", api: :dummy do
|
|
62
|
+
let(:api) { Conjur::API.new_from_key('user', 'pass') }
|
|
63
|
+
|
|
64
|
+
before do
|
|
65
|
+
allow(api).to receive(:credentials).and_return('the-credentials')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "raises FeatureNotAvailable for a SaaS appliance" do
|
|
69
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://x.secretsmgr.cyberark.cloud')
|
|
70
|
+
|
|
71
|
+
expect { api.dry_run_policy('root', 'policy body', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /SaaS/)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "raises FeatureNotAvailable for a server older than 1.21.1" do
|
|
75
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
76
|
+
allow(api).to receive(:server_version).and_return('1.20.0')
|
|
77
|
+
|
|
78
|
+
expect { api.dry_run_policy('root', 'policy body', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /1\.21\.1/)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "validates a policy load without applying it, defaulting to POST" do
|
|
82
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
83
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
84
|
+
|
|
85
|
+
resource = instance_double(RestClient::Resource, "policy resource")
|
|
86
|
+
allow(api).to receive(:url_for)
|
|
87
|
+
.with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
|
|
88
|
+
.and_return(resource)
|
|
89
|
+
|
|
90
|
+
allow(resource).to receive(:post).with('policy body').and_return(
|
|
91
|
+
'{"status":"Valid YAML","created":{"items":[]},"updated":{"before":{"items":[]},"after":{"items":[]}},"deleted":{"items":[]}}'
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
result = api.dry_run_policy('root', 'policy body', account: 'the-account')
|
|
95
|
+
|
|
96
|
+
expect(result['status']).to eq('Valid YAML')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "supports POLICY_METHOD_PUT" do
|
|
100
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
101
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
102
|
+
|
|
103
|
+
resource = instance_double(RestClient::Resource, "policy resource")
|
|
104
|
+
allow(api).to receive(:url_for)
|
|
105
|
+
.with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
|
|
106
|
+
.and_return(resource)
|
|
107
|
+
|
|
108
|
+
allow(resource).to receive(:put).with('policy body').and_return(
|
|
109
|
+
'{"status":"Valid YAML"}'
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
result = api.dry_run_policy(
|
|
113
|
+
'root', 'policy body', account: 'the-account', method: Conjur::API::POLICY_METHOD_PUT
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
expect(result['status']).to eq('Valid YAML')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "reports invalid policy YAML instead of raising" do
|
|
120
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
121
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
122
|
+
|
|
123
|
+
resource = instance_double(RestClient::Resource, "policy resource")
|
|
124
|
+
allow(api).to receive(:url_for)
|
|
125
|
+
.with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
|
|
126
|
+
.and_return(resource)
|
|
127
|
+
|
|
128
|
+
response_body = '{"status":"Invalid YAML","errors":[{"line":1,"column":10,"message":"bad yaml"}]}'
|
|
129
|
+
response = instance_double(RestClient::Response, "error response", body: response_body, code: 422)
|
|
130
|
+
exception = RestClient::UnprocessableEntity.new(response)
|
|
131
|
+
allow(exception).to receive(:response).and_return(response)
|
|
132
|
+
allow(resource).to receive(:post).with('- !group [invalid').and_raise(exception)
|
|
133
|
+
|
|
134
|
+
result = api.dry_run_policy('root', '- !group [invalid', account: 'the-account')
|
|
135
|
+
|
|
136
|
+
expect(result['status']).to eq('Invalid YAML')
|
|
137
|
+
expect(result['errors'].first['message']).to eq('bad yaml')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "raises for other error responses instead of returning them as a result" do
|
|
141
|
+
allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
|
|
142
|
+
allow(api).to receive(:server_version).and_return('1.21.1')
|
|
143
|
+
|
|
144
|
+
resource = instance_double(RestClient::Resource, "policy resource")
|
|
145
|
+
allow(api).to receive(:url_for)
|
|
146
|
+
.with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
|
|
147
|
+
.and_return(resource)
|
|
148
|
+
|
|
149
|
+
response = instance_double(RestClient::Response, "error response", body: '{"error":"forbidden"}', code: 403)
|
|
150
|
+
exception = RestClient::Forbidden.new(response)
|
|
151
|
+
allow(exception).to receive(:response).and_return(response)
|
|
152
|
+
allow(resource).to receive(:post).with('policy body').and_raise(exception)
|
|
153
|
+
|
|
154
|
+
expect {
|
|
155
|
+
api.dry_run_policy('root', 'policy body', account: 'the-account')
|
|
156
|
+
}.to raise_error(RestClient::Forbidden)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'conjur/api/router'
|
|
5
|
+
|
|
6
|
+
describe Conjur::API::Router do
|
|
7
|
+
before do
|
|
8
|
+
allow(Conjur.configuration).to receive(:core_url).and_return('http://core.example.com')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe ".server_info" do
|
|
12
|
+
it "builds an unauthenticated resource for the info endpoint" do
|
|
13
|
+
resource = Conjur::API::Router.server_info
|
|
14
|
+
|
|
15
|
+
expect(resource.url).to eq('http://core.example.com/info')
|
|
16
|
+
expect(resource.options[:headers]).not_to have_key(:authorization)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe ".server_root" do
|
|
21
|
+
it "builds an unauthenticated resource for the root endpoint" do
|
|
22
|
+
resource = Conjur::API::Router.server_root
|
|
23
|
+
|
|
24
|
+
expect(resource.url).to eq('http://core.example.com/')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe ".policies_load_policy" do
|
|
29
|
+
it "builds a resource with no trailing slash or query string" do
|
|
30
|
+
resource = Conjur::API::Router.policies_load_policy({}, 'the-account', 'root')
|
|
31
|
+
|
|
32
|
+
expect(resource.url).to eq('http://core.example.com/policies/the-account/policy/root')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe ".policies_dry_run_policy" do
|
|
37
|
+
it "builds a resource with the dryRun query string" do
|
|
38
|
+
resource = Conjur::API::Router.policies_dry_run_policy({}, 'the-account', 'root')
|
|
39
|
+
|
|
40
|
+
expect(resource.url).to eq('http://core.example.com/policies/the-account/policy/root/?dryRun=true')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'conjur/api/server_version'
|
|
5
|
+
|
|
6
|
+
describe "Conjur::API#server_version", api: :dummy do
|
|
7
|
+
let(:info_resource) { instance_double(RestClient::Resource, "info resource") }
|
|
8
|
+
let(:root_resource) { instance_double(RestClient::Resource, "root resource") }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
allow(Conjur::API::Router).to receive(:server_info).and_return(info_resource)
|
|
12
|
+
allow(Conjur::API::Router).to receive(:server_root).and_return(root_resource)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns the possum service version from the /info endpoint" do
|
|
16
|
+
body = {
|
|
17
|
+
"services" => { "possum" => { "version" => "1.21.1.1-25" } }
|
|
18
|
+
}.to_json
|
|
19
|
+
allow(info_resource).to receive(:get).and_return(
|
|
20
|
+
instance_double(RestClient::Response, "info response", body: body)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
expect(api.server_version).to eq("1.21.1.1-25")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "falls back to root when /info responds 404 (OSS)" do
|
|
27
|
+
response = instance_double(RestClient::Response, "404 response", body: '', code: 404)
|
|
28
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(response))
|
|
29
|
+
|
|
30
|
+
root_body = { "version" => "1.20.0" }.to_json
|
|
31
|
+
allow(root_resource).to receive(:get).and_return(
|
|
32
|
+
instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json' })
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
expect(api.server_version).to eq("1.20.0")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "falls back to root when /info responds 500 (not just 404/401)" do
|
|
39
|
+
response = instance_double(RestClient::Response, "500 response", body: '', code: 500)
|
|
40
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(response))
|
|
41
|
+
|
|
42
|
+
root_body = { "version" => "1.20.0" }.to_json
|
|
43
|
+
allow(root_resource).to receive(:get).and_return(
|
|
44
|
+
instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json' })
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
expect(api.server_version).to eq("1.20.0")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "parses a JSON root response even when Content-Type includes a charset" do
|
|
51
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
|
|
52
|
+
|
|
53
|
+
root_body = { "version" => "1.20.0" }.to_json
|
|
54
|
+
allow(root_resource).to receive(:get).and_return(
|
|
55
|
+
instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json; charset=utf-8' })
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
expect(api.server_version).to eq("1.20.0")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "parses an HTML root response when Content-Type is not JSON" do
|
|
62
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
|
|
63
|
+
|
|
64
|
+
root_body = "<html><body><dd>Version 1.19.0-359</dd></body></html>"
|
|
65
|
+
allow(root_resource).to receive(:get).and_return(
|
|
66
|
+
instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'text/html' })
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
expect(api.server_version).to eq("1.19.0-359")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "parses an HTML root response with the Version label and value in separate tags" do
|
|
73
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
|
|
74
|
+
|
|
75
|
+
root_body = "<html><body><dt>Version</dt><dd>1.28.0-1443</dd></body></html>"
|
|
76
|
+
allow(root_resource).to receive(:get).and_return(
|
|
77
|
+
instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'text/html' })
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
expect(api.server_version).to eq("1.28.0-1443")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "raises FeatureNotAvailable when neither endpoint yields a version" do
|
|
84
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
|
|
85
|
+
allow(root_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(instance_double(RestClient::Response, body: '', code: 500)))
|
|
86
|
+
|
|
87
|
+
expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable, /Unable to determine Conjur server version/)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "memoizes a successful lookup" do
|
|
91
|
+
body = { "services" => { "possum" => { "version" => "1.21.1" } } }.to_json
|
|
92
|
+
allow(info_resource).to receive(:get).and_return(
|
|
93
|
+
instance_double(RestClient::Response, "info response", body: body)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
api.server_version
|
|
97
|
+
api.server_version
|
|
98
|
+
|
|
99
|
+
expect(info_resource).to have_received(:get).once
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "does not memoize a failed lookup, retrying on the next call" do
|
|
103
|
+
allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
|
|
104
|
+
allow(root_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(instance_double(RestClient::Response, body: '', code: 500)))
|
|
105
|
+
|
|
106
|
+
expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable)
|
|
107
|
+
expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable)
|
|
108
|
+
|
|
109
|
+
expect(info_resource).to have_received(:get).twice
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'conjur/api/version_check'
|
|
5
|
+
|
|
6
|
+
describe "Conjur::API#verify_min_server_version!", api: :dummy do
|
|
7
|
+
it "passes when the server version equals the minimum" do
|
|
8
|
+
allow(api).to receive(:server_version).and_return("1.21.1")
|
|
9
|
+
|
|
10
|
+
expect(api.verify_min_server_version!("1.21.1")).to be true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "passes when the server version is greater than the minimum" do
|
|
14
|
+
allow(api).to receive(:server_version).and_return("1.22.0")
|
|
15
|
+
|
|
16
|
+
expect(api.verify_min_server_version!("1.21.1")).to be true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "passes when a Conjur build suffix is present and the base version is sufficient" do
|
|
20
|
+
allow(api).to receive(:server_version).and_return("1.21.1.1-25")
|
|
21
|
+
|
|
22
|
+
expect(api.verify_min_server_version!("1.21.1")).to be true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "raises when the server version is less than the minimum" do
|
|
26
|
+
allow(api).to receive(:server_version).and_return("1.20.0")
|
|
27
|
+
|
|
28
|
+
expect { api.verify_min_server_version!("1.21.1") }.to raise_error(
|
|
29
|
+
Conjur::FeatureNotAvailable, /1\.20\.0.*1\.21\.1|1\.21\.1.*1\.20\.0/
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "raises FeatureNotAvailable (not ArgumentError) for an unparseable server version" do
|
|
34
|
+
allow(api).to receive(:server_version).and_return("development")
|
|
35
|
+
|
|
36
|
+
expect { api.verify_min_server_version!("1.21.1") }.to raise_error(
|
|
37
|
+
Conjur::FeatureNotAvailable, /Unable to parse Conjur server version/
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'conjur/saas'
|
|
5
|
+
|
|
6
|
+
describe Conjur::Saas do
|
|
7
|
+
describe ".appliance_url?" do
|
|
8
|
+
it "matches a .secretsmgr SaaS hostname" do
|
|
9
|
+
expect(Conjur::Saas.appliance_url?("https://myorg.secretsmgr.cyberark.cloud")).to be true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "matches a -secretsmanager SaaS hostname" do
|
|
13
|
+
expect(Conjur::Saas.appliance_url?("https://myorg-secretsmanager.dev-cyberark.cloud")).to be true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "matches with additional subdomains before the prefix" do
|
|
17
|
+
expect(Conjur::Saas.appliance_url?("https://tenant.myorg.secretsmgr.sandbox-cyberark.cloud")).to be true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "does not match a plain appliance URL" do
|
|
21
|
+
expect(Conjur::Saas.appliance_url?("https://conjur.example.com/api")).to be false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "does not match when the scheme is not https" do
|
|
25
|
+
expect(Conjur::Saas.appliance_url?("http://myorg.secretsmgr.cyberark.cloud")).to be false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "does not match a nil url" do
|
|
29
|
+
expect(Conjur::Saas.appliance_url?(nil)).to be false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "does not raise for a malformed url and returns false" do
|
|
33
|
+
expect(Conjur::Saas.appliance_url?("not a url with spaces")).to be false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "does not match a relative or opaque url with no host" do
|
|
37
|
+
expect(Conjur::Saas.appliance_url?("mailto:someone@example.com")).to be false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
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: 6.3.
|
|
4
|
+
version: 6.3.1.pre.819
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- CyberArk Maintainers
|
|
@@ -350,7 +350,9 @@ files:
|
|
|
350
350
|
- lib/conjur/api/resources.rb
|
|
351
351
|
- lib/conjur/api/roles.rb
|
|
352
352
|
- lib/conjur/api/router.rb
|
|
353
|
+
- lib/conjur/api/server_version.rb
|
|
353
354
|
- lib/conjur/api/variables.rb
|
|
355
|
+
- lib/conjur/api/version_check.rb
|
|
354
356
|
- lib/conjur/base.rb
|
|
355
357
|
- lib/conjur/base_object.rb
|
|
356
358
|
- lib/conjur/build_object.rb
|
|
@@ -378,6 +380,7 @@ files:
|
|
|
378
380
|
- lib/conjur/role.rb
|
|
379
381
|
- lib/conjur/role_grant.rb
|
|
380
382
|
- lib/conjur/routing.rb
|
|
383
|
+
- lib/conjur/saas.rb
|
|
381
384
|
- lib/conjur/user.rb
|
|
382
385
|
- lib/conjur/variable.rb
|
|
383
386
|
- lib/conjur/webservice.rb
|
|
@@ -385,6 +388,9 @@ files:
|
|
|
385
388
|
- spec/.conjurrc
|
|
386
389
|
- spec/api/host_factories_spec.rb
|
|
387
390
|
- spec/api/policies_spec.rb
|
|
391
|
+
- spec/api/router_spec.rb
|
|
392
|
+
- spec/api/server_version_spec.rb
|
|
393
|
+
- spec/api/version_check_spec.rb
|
|
388
394
|
- spec/api_spec.rb
|
|
389
395
|
- spec/authn_cert_spec.rb
|
|
390
396
|
- spec/base_object_spec.rb
|
|
@@ -405,6 +411,7 @@ files:
|
|
|
405
411
|
- spec/spec_helper.rb
|
|
406
412
|
- spec/ssl_spec.rb
|
|
407
413
|
- spec/unit/policy_load_result_spec.rb
|
|
414
|
+
- spec/unit/saas_spec.rb
|
|
408
415
|
- spec/uri_escape_spec.rb
|
|
409
416
|
- test.sh
|
|
410
417
|
- tmp/.keep
|
|
@@ -460,6 +467,9 @@ test_files:
|
|
|
460
467
|
- spec/.conjurrc
|
|
461
468
|
- spec/api/host_factories_spec.rb
|
|
462
469
|
- spec/api/policies_spec.rb
|
|
470
|
+
- spec/api/router_spec.rb
|
|
471
|
+
- spec/api/server_version_spec.rb
|
|
472
|
+
- spec/api/version_check_spec.rb
|
|
463
473
|
- spec/api_spec.rb
|
|
464
474
|
- spec/authn_cert_spec.rb
|
|
465
475
|
- spec/base_object_spec.rb
|
|
@@ -480,4 +490,5 @@ test_files:
|
|
|
480
490
|
- spec/spec_helper.rb
|
|
481
491
|
- spec/ssl_spec.rb
|
|
482
492
|
- spec/unit/policy_load_result_spec.rb
|
|
493
|
+
- spec/unit/saas_spec.rb
|
|
483
494
|
- spec/uri_escape_spec.rb
|