conjur-api 6.3.1 → 6.4.0.pre.849

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.
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'cgi'
5
+ require 'conjur/api/router'
6
+
7
+ describe Conjur::API, api: :dummy do
8
+ let(:identity) { 'host/data/test/gcp-apps/test-app' }
9
+ let(:gcp_jwt) { 'eyJhbGciOiJSUzI1NiJ9.gcp-payload.sig' }
10
+ let(:raw_token) { { 'protected' => 'p', 'payload' => 'pl', 'signature' => 's' } }
11
+
12
+ describe '.new_from_gcp' do
13
+ it 'returns an API instance with a GCPAuthenticator' do
14
+ allow(Conjur::API).to receive(:authenticate_gcp).and_return(raw_token)
15
+ api_instance = Conjur::API.new_from_gcp(identity, account: account)
16
+ expect(api_instance).to be_a(Conjur::API)
17
+ expect(api_instance.authenticator).to be_a(Conjur::API::GCPAuthenticator)
18
+ end
19
+ end
20
+
21
+ describe '.authenticate_gcp' do
22
+ let(:resource) { double('resource') }
23
+
24
+ before do
25
+ allow(Conjur::API).to receive(:url_for)
26
+ .with(:authn_gcp_authenticate, account)
27
+ .and_return(resource)
28
+ allow(resource).to receive(:post)
29
+ .with("jwt=#{CGI.escape(gcp_jwt)}", content_type: 'application/x-www-form-urlencoded')
30
+ .and_return(raw_token.to_json)
31
+ end
32
+
33
+ context 'with a pre-supplied JWT' do
34
+ it 'returns a parsed token without calling the metadata service' do
35
+ expect(Conjur::API).not_to receive(:fetch_gcp_jwt)
36
+ token = Conjur::API.authenticate_gcp(identity, account: account, jwt: gcp_jwt)
37
+ expect(token).to eq(raw_token)
38
+ end
39
+ end
40
+
41
+ context 'without a pre-supplied JWT' do
42
+ it 'fetches from the metadata service' do
43
+ allow(Conjur::API).to receive(:fetch_gcp_jwt).and_return(gcp_jwt)
44
+ token = Conjur::API.authenticate_gcp(identity, account: account)
45
+ expect(token).to eq(raw_token)
46
+ end
47
+ end
48
+
49
+ it 'raises ArgumentError when identity is empty' do
50
+ expect { Conjur::API.authenticate_gcp('', account: account) }
51
+ .to raise_error(ArgumentError, /identity is required/)
52
+ end
53
+ end
54
+
55
+ describe 'GCPAuthenticator#refresh_token' do
56
+ context 'with a pre-supplied JWT' do
57
+ subject(:authenticator) do
58
+ Conjur::API::GCPAuthenticator.new(account, identity, jwt: gcp_jwt)
59
+ end
60
+
61
+ it 'passes the stored JWT to authenticate_gcp' do
62
+ expect(Conjur::API).to receive(:authenticate_gcp)
63
+ .with(identity,
64
+ account: account,
65
+ jwt: gcp_jwt,
66
+ gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL)
67
+ .and_return(raw_token)
68
+ expect(authenticator.refresh_token).to eq(raw_token)
69
+ end
70
+ end
71
+
72
+ context 'without a pre-supplied JWT' do
73
+ subject(:authenticator) do
74
+ Conjur::API::GCPAuthenticator.new(account, identity)
75
+ end
76
+
77
+ it 'passes nil jwt to authenticate_gcp (which then calls metadata service)' do
78
+ expect(Conjur::API).to receive(:authenticate_gcp)
79
+ .with(identity,
80
+ account: account,
81
+ jwt: nil,
82
+ gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL)
83
+ .and_return(raw_token)
84
+ expect(authenticator.refresh_token).to eq(raw_token)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '.gcp_audience' do
90
+ it 'strips a leading host/ and rebuilds the conjur audience' do
91
+ expect(Conjur::API.send(:gcp_audience, account, 'host/data/app'))
92
+ .to eq("conjur/#{account}/host/data/app")
93
+ end
94
+
95
+ it 'assumes a host identity when the host/ prefix is absent' do
96
+ expect(Conjur::API.send(:gcp_audience, account, 'data/app'))
97
+ .to eq("conjur/#{account}/host/data/app")
98
+ end
99
+ end
100
+ end
101
+
102
+ describe Conjur::API::Router do
103
+ let(:account) { 'myaccount' }
104
+
105
+ before do
106
+ allow(Conjur.configuration).to receive(:core_url).and_return('https://conjur.example.com')
107
+ allow(Conjur.configuration).to receive(:rest_client_options).and_return({})
108
+ end
109
+
110
+ describe '#authn_gcp_authenticate' do
111
+ subject { described_class.authn_gcp_authenticate(account) }
112
+
113
+ it 'returns a RestClient::Resource' do
114
+ expect(subject).to be_a(RestClient::Resource)
115
+ end
116
+
117
+ it 'builds the correct URL using the serviceless authn-gcp path' do
118
+ expect(subject.url).to end_with('/authn-gcp/myaccount/authenticate')
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'conjur/api/router'
5
+
6
+ describe Conjur::API, api: :dummy do
7
+ let(:service_id) { 'prod' }
8
+ let(:identity) { 'host/my-app/my-role' }
9
+ let(:raw_token) { { 'protected' => 'p', 'payload' => 'pl', 'signature' => 's' } }
10
+ let(:signed_headers) do
11
+ {
12
+ 'Authorization' => 'AWS4-HMAC-SHA256 Credential=AKIAEXAMPLE/...',
13
+ 'x-amz-date' => '20240101T000000Z',
14
+ 'x-amz-content-sha256' => 'e3b0c44298fc1c149afbf4c8996fb924',
15
+ 'x-amz-security-token' => 'token',
16
+ 'host' => 'sts.amazonaws.com'
17
+ }
18
+ end
19
+
20
+ describe '.new_from_iam' do
21
+ it 'returns an API instance with an IAMAuthenticator' do
22
+ allow(Conjur::API).to receive(:authenticate_iam).and_return(raw_token)
23
+ api_instance = Conjur::API.new_from_iam(identity, account: account)
24
+ expect(api_instance).to be_a(Conjur::API)
25
+ expect(api_instance.authenticator).to be_a(Conjur::API::IAMAuthenticator)
26
+ end
27
+ end
28
+
29
+ describe '.authenticate_iam' do
30
+ let(:resource) { double('resource') }
31
+
32
+ before do
33
+ allow(Conjur::API).to receive(:require).with('aws-sdk-core')
34
+ allow(Conjur::API).to receive(:url_for)
35
+ .with(:authn_iam_authenticate, account, service_id, identity)
36
+ .and_return(resource)
37
+ allow(resource).to receive(:post)
38
+ .with(signed_headers.to_json, content_type: 'application/json')
39
+ .and_return(raw_token.to_json)
40
+ allow(Conjur::API).to receive(:build_iam_signed_headers).and_return(signed_headers)
41
+ end
42
+
43
+ it 'returns a parsed token' do
44
+ token = Conjur::API.authenticate_iam(identity, account: account, service_id: service_id)
45
+ expect(token).to eq(raw_token)
46
+ end
47
+
48
+ it 'raises ArgumentError when identity is empty' do
49
+ expect { Conjur::API.authenticate_iam('', account: account) }
50
+ .to raise_error(ArgumentError, /identity is required/)
51
+ end
52
+
53
+ it 'raises ArgumentError when service_id is empty' do
54
+ expect { Conjur::API.authenticate_iam(identity, account: account, service_id: '') }
55
+ .to raise_error(ArgumentError, /service_id is required/)
56
+ end
57
+
58
+ it 'raises LoadError when aws-sdk-core is unavailable' do
59
+ allow(Conjur::API).to receive(:require).with('aws-sdk-core').and_raise(LoadError)
60
+ expect { Conjur::API.authenticate_iam(identity, account: account) }
61
+ .to raise_error(LoadError, /aws-sdk-core/)
62
+ end
63
+ end
64
+
65
+ describe 'IAMAuthenticator#refresh_token' do
66
+ subject(:authenticator) do
67
+ Conjur::API::IAMAuthenticator.new(account, service_id, identity)
68
+ end
69
+
70
+ it 'delegates to authenticate_iam with stored credentials' do
71
+ expect(Conjur::API).to receive(:authenticate_iam)
72
+ .with(identity,
73
+ account: account,
74
+ service_id: service_id,
75
+ aws_region: Conjur::API::IAMAuthenticator::DEFAULT_REGION)
76
+ .and_return(raw_token)
77
+ expect(authenticator.refresh_token).to eq(raw_token)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe Conjur::API::Router do
83
+ let(:account) { 'myaccount' }
84
+ let(:service_id) { 'prod' }
85
+ let(:identity) { 'host/my-app/my-role' }
86
+
87
+ before do
88
+ allow(Conjur.configuration).to receive(:core_url).and_return('https://conjur.example.com')
89
+ allow(Conjur.configuration).to receive(:rest_client_options).and_return({})
90
+ end
91
+
92
+ describe '#authn_iam_authenticate' do
93
+ subject { described_class.authn_iam_authenticate(account, service_id, identity) }
94
+
95
+ it 'returns a RestClient::Resource' do
96
+ expect(subject).to be_a(RestClient::Resource)
97
+ end
98
+
99
+ it 'builds the correct URL' do
100
+ expect(subject.url).to end_with('/authn-iam/prod/myaccount/host%2Fmy-app%2Fmy-role/authenticate')
101
+ end
102
+ end
103
+ end
data/test.sh CHANGED
@@ -7,6 +7,22 @@ RUBY_VERSION="$(cut -d '-' -f 2 <<< "$RUBY_VERSION")"
7
7
  export REGISTRY_URL=${INFRAPOOL_REGISTRY_URL:-"docker.io"}
8
8
  export RUBY_VERSION="${INFRAPOOL_RUBY_VERSION:-$RUBY_VERSION}"
9
9
 
10
+ # Cloud-authenticator integration toggles. These accept both plain and
11
+ # INFRAPOOL_-prefixed variants (Jenkins injects the latter). When any is
12
+ # "true", ONLY that authenticator's tagged feature runs against real cloud
13
+ # infrastructure; the normal suite is skipped. Left unset by default so PR
14
+ # builds run the ordinary unit + cucumber suite.
15
+ RUN_AWS_TESTS="${INFRAPOOL_RUN_AWS_TESTS:-${RUN_AWS_TESTS:-false}}"
16
+ RUN_AZURE_TESTS="${INFRAPOOL_RUN_AZURE_TESTS:-${RUN_AZURE_TESTS:-false}}"
17
+ RUN_GCP_TESTS="${INFRAPOOL_RUN_GCP_TESTS:-${RUN_GCP_TESTS:-false}}"
18
+ export RUN_AWS_TESTS RUN_AZURE_TESTS RUN_GCP_TESTS
19
+
20
+ # authn-iam identifies the Conjur host by the AWS caller identity; the AWS stage
21
+ # derives these from `aws sts get-caller-identity` on the agent.
22
+ AWS_ACCOUNT_ID="${INFRAPOOL_AWS_ACCOUNT_ID:-${AWS_ACCOUNT_ID:-}}"
23
+ AWS_ROLE_NAME="${INFRAPOOL_AWS_ROLE_NAME:-${AWS_ROLE_NAME:-}}"
24
+ export AWS_ACCOUNT_ID AWS_ROLE_NAME
25
+
10
26
  source ./ci/oauth/keycloak/keycloak_functions.sh
11
27
  TOP_LEVEL=$(git rev-parse --show-toplevel)
12
28
 
@@ -23,6 +39,23 @@ if [ ! -f "${TOP_LEVEL}/VERSION" ]; then
23
39
  echo -n "0.0.dev" > "${TOP_LEVEL}/VERSION"
24
40
  fi
25
41
 
42
+ # Returns the cloud-authn cucumber tag to run when a RUN_*_TESTS flag is set,
43
+ # or empty for a normal run. Validates required env vars for the chosen cloud.
44
+ function cloudAuthnTag() {
45
+ if [[ "$RUN_AWS_TESTS" == "true" ]]; then
46
+ : "${AWS_ACCOUNT_ID:?AWS_ACCOUNT_ID is required for IAM tests (derived from aws sts get-caller-identity)}"
47
+ : "${AWS_ROLE_NAME:?AWS_ROLE_NAME is required for IAM tests (derived from aws sts get-caller-identity)}"
48
+ echo '@authn_iam'
49
+ elif [[ "$RUN_AZURE_TESTS" == "true" ]]; then
50
+ : "${AZURE_SUBSCRIPTION_ID:?AZURE_SUBSCRIPTION_ID is required for Azure tests}"
51
+ : "${AZURE_RESOURCE_GROUP:?AZURE_RESOURCE_GROUP is required for Azure tests}"
52
+ echo '@authn_azure'
53
+ elif [[ "$RUN_GCP_TESTS" == "true" ]]; then
54
+ : "${GCP_ID_TOKEN:?GCP_ID_TOKEN is required for GCP tests (run ci/get_gcp_token.sh first)}"
55
+ echo '@authn_gcp'
56
+ fi
57
+ }
58
+
26
59
  function main() {
27
60
  if ! docker info >/dev/null 2>&1; then
28
61
  echo "Docker does not seem to be running, run it first and retry"
@@ -31,6 +64,8 @@ function main() {
31
64
  # Generate reports folders locally
32
65
  mkdir -p spec/reports features/reports
33
66
 
67
+ CLOUD_AUTHN_TAG="$(cloudAuthnTag)"
68
+
34
69
  startConjur
35
70
  runTests
36
71
  }
@@ -54,6 +89,15 @@ function runTests() {
54
89
 
55
90
  local api_key=$(docker compose exec -T conjur conjurctl role retrieve-key cucumber:user:admin | tr -d '\r\n')
56
91
 
92
+ if [[ -n "$CLOUD_AUTHN_TAG" ]]; then
93
+ runCloudAuthnTests "$api_key"
94
+ else
95
+ runDefaultTests "$api_key"
96
+ fi
97
+ }
98
+
99
+ function runDefaultTests() {
100
+ local api_key="$1"
57
101
  echo 'Running tests'
58
102
  echo '-----'
59
103
  docker compose run --rm \
@@ -63,4 +107,38 @@ function runTests() {
63
107
  "/scripts/fetch_certificate && bundle exec rake jenkins_init jenkins_spec jenkins_cucumber"
64
108
  }
65
109
 
110
+ # Runs a single cloud-authenticator feature against real cloud infrastructure.
111
+ # Forwards the cloud provider env vars and clears proxies so the container can
112
+ # reach the link-local metadata endpoints (169.254.169.254, metadata.google.internal).
113
+ function runCloudAuthnTests() {
114
+ local api_key="$1"
115
+ echo "Running cloud authenticator tests: $CLOUD_AUTHN_TAG"
116
+ echo '-----'
117
+ local rc=0
118
+ docker compose run --rm \
119
+ -e CONJUR_AUTHN_API_KEY="$api_key" \
120
+ -e CLOUD_AUTHN_TAG="$CLOUD_AUTHN_TAG" \
121
+ -e NO_PROXY="169.254.169.254,metadata.google.internal,localhost,127.0.0.1,conjur" \
122
+ -e HTTP_PROXY="" -e HTTPS_PROXY="" -e http_proxy="" -e https_proxy="" \
123
+ -e AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-}" \
124
+ -e AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-}" \
125
+ -e USER_ASSIGNED_IDENTITY_CLIENT_ID="${USER_ASSIGNED_IDENTITY_CLIENT_ID:-}" \
126
+ -e GCP_ID_TOKEN="${GCP_ID_TOKEN:-}" \
127
+ -e GCP_PROJECT_ID="${GCP_PROJECT_ID:-}" \
128
+ -e AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID:-}" \
129
+ -e AWS_ROLE_NAME="${AWS_ROLE_NAME:-}" \
130
+ tester \
131
+ "bundle exec rake jenkins_init jenkins_cucumber_cloud" || rc=$?
132
+
133
+ # On failure, dump the Conjur server log: authn-azure/iam/gcp rejections are
134
+ # logged there with the precise reason (issuer/annotation/audience mismatch),
135
+ # which the client only ever sees as an opaque 401.
136
+ if [[ $rc -ne 0 ]]; then
137
+ echo "=== cloud authn test failed; last 100 lines of Conjur server log ==="
138
+ docker compose logs --tail=100 conjur || true
139
+ echo '=== end Conjur server log ==='
140
+ fi
141
+ return $rc
142
+ }
143
+
66
144
  main
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.1
4
+ version: 6.4.0.pre.849
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberArk Maintainers
@@ -261,6 +261,20 @@ dependencies:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
263
  version: '0'
264
+ - !ruby/object:Gem::Dependency
265
+ name: aws-sdk-core
266
+ requirement: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
271
+ type: :development
272
+ prerelease: false
273
+ version_requirements: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - ">="
276
+ - !ruby/object:Gem::Version
277
+ version: '0'
264
278
  description: Conjur API
265
279
  email:
266
280
  - conj_maintainers@cyberark.com
@@ -291,6 +305,7 @@ files:
291
305
  - SECURITY.md
292
306
  - VERSION
293
307
  - ci/configure.sh
308
+ - ci/get_gcp_token.sh
294
309
  - ci/oauth/keycloak/create_client
295
310
  - ci/oauth/keycloak/create_user
296
311
  - ci/oauth/keycloak/fetch_certificate
@@ -305,8 +320,12 @@ files:
305
320
  - dev/stop
306
321
  - docker-compose.yml
307
322
  - example/demo.rb
323
+ - features/README.md
308
324
  - features/authenticators.feature
309
325
  - features/authn.feature
326
+ - features/authn_azure.feature
327
+ - features/authn_gcp.feature
328
+ - features/authn_iam.feature
310
329
  - features/authn_local.feature
311
330
  - features/exists.feature
312
331
  - features/group.feature
@@ -322,6 +341,7 @@ files:
322
341
  - features/role_fields.feature
323
342
  - features/rotate_api_key.feature
324
343
  - features/step_definitions/api_steps.rb
344
+ - features/step_definitions/cloud_authn_steps.rb
325
345
  - features/step_definitions/policy_steps.rb
326
346
  - features/step_definitions/result_steps.rb
327
347
  - features/support/env.rb
@@ -342,7 +362,10 @@ files:
342
362
  - lib/conjur/api.rb
343
363
  - lib/conjur/api/authenticators.rb
344
364
  - lib/conjur/api/authn.rb
365
+ - lib/conjur/api/authn_azure.rb
345
366
  - lib/conjur/api/authn_cert.rb
367
+ - lib/conjur/api/authn_gcp.rb
368
+ - lib/conjur/api/authn_iam.rb
346
369
  - lib/conjur/api/host_factories.rb
347
370
  - lib/conjur/api/ldap_sync.rb
348
371
  - lib/conjur/api/policies.rb
@@ -385,6 +408,7 @@ files:
385
408
  - lib/conjur/variable.rb
386
409
  - lib/conjur/webservice.rb
387
410
  - publish.sh
411
+ - secrets.yml
388
412
  - spec/.conjurrc
389
413
  - spec/api/host_factories_spec.rb
390
414
  - spec/api/policies_spec.rb
@@ -392,7 +416,10 @@ files:
392
416
  - spec/api/server_version_spec.rb
393
417
  - spec/api/version_check_spec.rb
394
418
  - spec/api_spec.rb
419
+ - spec/authn_azure_spec.rb
395
420
  - spec/authn_cert_spec.rb
421
+ - spec/authn_gcp_spec.rb
422
+ - spec/authn_iam_spec.rb
396
423
  - spec/base_object_spec.rb
397
424
  - spec/cert_utils_spec.rb
398
425
  - spec/cidr_spec.rb
@@ -437,8 +464,12 @@ rubygems_version: 4.0.10
437
464
  specification_version: 4
438
465
  summary: Conjur API
439
466
  test_files:
467
+ - features/README.md
440
468
  - features/authenticators.feature
441
469
  - features/authn.feature
470
+ - features/authn_azure.feature
471
+ - features/authn_gcp.feature
472
+ - features/authn_iam.feature
442
473
  - features/authn_local.feature
443
474
  - features/exists.feature
444
475
  - features/group.feature
@@ -454,6 +485,7 @@ test_files:
454
485
  - features/role_fields.feature
455
486
  - features/rotate_api_key.feature
456
487
  - features/step_definitions/api_steps.rb
488
+ - features/step_definitions/cloud_authn_steps.rb
457
489
  - features/step_definitions/policy_steps.rb
458
490
  - features/step_definitions/result_steps.rb
459
491
  - features/support/env.rb
@@ -471,7 +503,10 @@ test_files:
471
503
  - spec/api/server_version_spec.rb
472
504
  - spec/api/version_check_spec.rb
473
505
  - spec/api_spec.rb
506
+ - spec/authn_azure_spec.rb
474
507
  - spec/authn_cert_spec.rb
508
+ - spec/authn_gcp_spec.rb
509
+ - spec/authn_iam_spec.rb
475
510
  - spec/base_object_spec.rb
476
511
  - spec/cert_utils_spec.rb
477
512
  - spec/cidr_spec.rb