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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7b56e1d0e5d393556c564aec43aed82569aee4fce4506f2781af39308dde858
4
- data.tar.gz: 0e0f305d3ade218a69a327e0e7bb394ec70317719897942925fe13d4296a63b5
3
+ metadata.gz: a1cdd341c320a370b2fe2a1acc7897da7cf46646b0f2e753c4a36210b9bedad5
4
+ data.tar.gz: 98e3471100dfd37156b37f1f60d8ff6dfb730879e3ad1f919cca1334a429a295
5
5
  SHA512:
6
- metadata.gz: 25cbc42d0c2b063b0eea9c575d2991fcb49c9097bb1b8b2389a3d718fe589aa0565b147053ec30aaa6696e0f340639fc8792ff8dfcc134dc9c075df9d6263178
7
- data.tar.gz: 3aec2b8ee3ad4b6a58206dd50d8e3b57c369e370559518ccfcea6b0ee3a0b83a955430dbc6d1dc346447e4f0f9ccb490da406cb20126e4fe38297f5311f8ad72
6
+ metadata.gz: e4db5cfec0074e9aad0cd89f5fd3331a70c664c0132d3bed6b6cc14baa08bb4b986e3dcd553340675887d627298daf146bd3a282d416f310e46640bd0c94c7b6
7
+ data.tar.gz: 28cd1578c740d4913764573d7ebcdc04b7008a33a43526bd07cfa8c725fa388b5bb47d846dd2a9d7aa391cd0a1b21c3fd8d4a9afa11e0016001c0383955411f7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,33 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## [6.4.0] - 2026-08-06
10
+ ### Added
11
+ - Cloud-native authenticators for Azure Managed Identity (`authn-azure`),
12
+ AWS IAM (`authn-iam`), and GCP workload identity (`authn-gcp`), exposed
13
+ via `Conjur::API.new_from_azure`, `.new_from_iam`, and `.new_from_gcp`
14
+ (CNJR-14213).
15
+ - Live integration features for the cloud authenticators, gated behind the
16
+ `RUN_AWS_TESTS` / `RUN_AZURE_TESTS` / `RUN_GCP_TESTS` pipeline params and run
17
+ on dedicated cloud agents (excluded from the default suite) (CNJR-14213).
18
+
19
+ ### Changed
20
+ - CI uses Conjur Enterprise 1.27.0+, which removed the `GET /public_keys` endpoint
21
+ (CNJR-11339). Dropped the Cucumber scenario that exercised that API; the client
22
+ method remains for open-source Conjur.
23
+ - Retrieve the Cucumber admin API key via `conjurctl` in `test.sh` instead of rake,
24
+ avoiding Rails boot output corrupting the key on current Conjur images.
25
+ - CI now tests against Ruby 3.3, 3.4, and 4.0. Dropped Ruby 3.2 (end of life)
26
+ and raised the minimum supported Ruby version accordingly.
27
+
28
+ ### Fixed
29
+ - Set `CONJUR_AUTHN_ALLOWED_IP_RANGES` and `CONJUR_AUTHN_HTTP_ALLOWLIST` in the
30
+ test environment so Conjur's new SSRF guard does not block the authn-oidc
31
+ `provider-uri` fetch to the compose-local Keycloak server. (CNJR-13786)
32
+ - Corrected `Conjur::API.new_from_gcp` / `authenticate_gcp` to target the
33
+ serviceless `authn-gcp` endpoint (`/authn-gcp/{account}/authenticate`) instead
34
+ of `authn-jwt`; the `service_id` argument was removed accordingly (CNJR-14213).
35
+
9
36
  ## [6.3.1] - 2026-07-16
10
37
 
11
38
  ### Added
@@ -24,15 +51,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
24
51
  - Added `Conjur::API#fetch_policy` for retrieving policy data from the server,
25
52
  as YAML or JSON, with optional tree depth and size limits.
26
53
 
27
- ### Changed
28
- - CI uses Conjur Enterprise 1.27.0+, which removed the `GET /public_keys` endpoint
29
- (CNJR-11339). Dropped the Cucumber scenario that exercised that API; the client
30
- method remains for open-source Conjur.
31
- - Retrieve the Cucumber admin API key via `conjurctl` in `test.sh` instead of rake,
32
- avoiding Rails boot output corrupting the key on current Conjur images.
33
- - CI now tests against Ruby 3.3, 3.4, and 4.0. Dropped Ruby 3.2 (end of life)
34
- and raised the minimum supported Ruby version accordingly.
35
-
36
54
  ## [6.2.0] - 2026-06-05
37
55
 
38
56
  ## [6.1.0] - 2025-12-10
data/Jenkinsfile CHANGED
@@ -30,8 +30,23 @@ pipeline {
30
30
  }
31
31
 
32
32
  triggers {
33
- cron(getDailyCronString())
34
- parameterizedCron(getWeeklyCronString("H(1-5)","%MODE=RELEASE"))
33
+ // Declarative pipelines allow only one parameterizedCron trigger, so both
34
+ // schedules live in a single multi-line string. The daily build also
35
+ // exercises the cloud authenticators end-to-end; PR builds leave the
36
+ // RUN_*_TESTS params false, so those stages are skipped there.
37
+ parameterizedCron("""
38
+ ${getDailyCronString("%RUN_AWS_TESTS=true;RUN_AZURE_TESTS=true;RUN_GCP_TESTS=true")}
39
+ ${getWeeklyCronString("H(1-5)","%MODE=RELEASE")}
40
+ """)
41
+ }
42
+
43
+ parameters {
44
+ booleanParam(name: 'RUN_AWS_TESTS', defaultValue: false,
45
+ description: 'Run the authn-iam integration test on an AWS executor agent')
46
+ booleanParam(name: 'RUN_AZURE_TESTS', defaultValue: false,
47
+ description: 'Run the authn-azure integration test on an Azure executor agent')
48
+ booleanParam(name: 'RUN_GCP_TESTS', defaultValue: false,
49
+ description: 'Run the authn-jwt/gcp integration test (token fetched on a GCP agent)')
35
50
  }
36
51
 
37
52
  stages {
@@ -61,6 +76,17 @@ pipeline {
61
76
  steps {
62
77
  script {
63
78
  infrapool = getInfraPoolAgent.connected(type: "ExecutorV2", quantity: 1, duration: 1)[0]
79
+ // Provision dedicated cloud agents only when the matching cloud test
80
+ // is requested. These are real Azure/GCP VMs whose managed identity /
81
+ // service account the authenticators exercise against the live
82
+ // metadata service. The AWS test reuses the standard ExecutorV2 agent
83
+ // (which itself runs in AWS with an instance profile).
84
+ if (params.RUN_AZURE_TESTS) {
85
+ azureInfrapool = getInfraPoolAgent.connected(type: "AzureExecutorV2", quantity: 1, duration: 1)[0]
86
+ }
87
+ if (params.RUN_GCP_TESTS) {
88
+ gcpInfrapool = getInfraPoolAgent.connected(type: "GcpExecutorV2", quantity: 1, duration: 1)[0]
89
+ }
64
90
  }
65
91
  }
66
92
  }
@@ -125,6 +151,12 @@ pipeline {
125
151
  script {
126
152
  infrapool.agentSh "./test.sh"
127
153
  infrapool.agentStash name: 'reports4.0', includes: '**/reports/*.xml'
154
+ // Stash coverage from the full suite now. The cloud stages below run
155
+ // ./test.sh again on this agent, and jenkins_init wipes and regenerates
156
+ // coverage/ from a single cloud feature (no rspec) — which would drop
157
+ // the reported coverage well below the quality gate. Capturing it here
158
+ // keeps the submitted coverage that of the complete unit+cucumber run.
159
+ infrapool.agentStash name: 'coverage', includes: '**/coverage/**'
128
160
  }
129
161
  }
130
162
  post {
@@ -134,16 +166,101 @@ pipeline {
134
166
  }
135
167
  }
136
168
 
137
- stage('Submit Coverage Report'){
138
- steps{
169
+ stage('Run AWS IAM tests') {
170
+ when { expression { params.RUN_AWS_TESTS } }
171
+ environment {
172
+ INFRAPOOL_RUN_AWS_TESTS = 'true'
173
+ INFRAPOOL_RUBY_VERSION = '3.3'
174
+ INFRAPOOL_REGISTRY_URL = "registry.tld"
175
+ }
176
+ steps {
139
177
  script {
140
- infrapool.agentStash name: 'coverage', includes: '**/coverage/**'
178
+ // authn-iam identifies the Conjur host by the AWS caller identity, so
179
+ // the host id in the policy must be "<account-id>/<role-name>". Derive
180
+ // those from the agent's own instance profile (as conjur-api-java does)
181
+ // and pass them to the feature via env.
182
+ def callerIdentityJson = infrapool.agentSh(script: 'aws sts get-caller-identity', returnStdout: true).trim()
183
+ def awsAccountId = (callerIdentityJson =~ /"Account"\s*:\s*"([^"]+)"/)[0][1]
184
+ def awsArn = (callerIdentityJson =~ /"Arn"\s*:\s*"([^"]+)"/)[0][1]
185
+ def awsRoleName = awsArn.tokenize('/')[1] // arn:...:assumed-role/<role>/<session>
186
+ infrapool.agentSh """
187
+ export INFRAPOOL_AWS_ACCOUNT_ID='${awsAccountId}'
188
+ export INFRAPOOL_AWS_ROLE_NAME='${awsRoleName}'
189
+ ./test.sh
190
+ """
191
+ }
192
+ }
193
+ post {
194
+ always {
195
+ script { infrapool.agentStash name: 'reports-aws', includes: '**/reports/*.xml' }
196
+ unstash 'reports-aws'
197
+ }
198
+ }
199
+ }
200
+
201
+ stage('Run Azure tests') {
202
+ when { expression { params.RUN_AZURE_TESTS } }
203
+ environment {
204
+ INFRAPOOL_RUN_AZURE_TESTS = 'true'
205
+ INFRAPOOL_RUBY_VERSION = '3.3'
206
+ INFRAPOOL_REGISTRY_URL = "registry.tld"
207
+ }
208
+ steps {
209
+ script {
210
+ // summon injects AZURE_SUBSCRIPTION_ID / AZURE_RESOURCE_GROUP /
211
+ // USER_ASSIGNED_IDENTITY_CLIENT_ID from the CI vault; the test hits
212
+ // the real Azure IMDS on this Azure VM.
213
+ azureInfrapool.agentSh "summon -e azure ./test.sh"
214
+ }
215
+ }
216
+ post {
217
+ always {
218
+ script { azureInfrapool.agentStash name: 'reports-azure', includes: '**/reports/*.xml' }
219
+ unstash 'reports-azure'
220
+ }
221
+ }
222
+ }
223
+
224
+ stage('Run GCP tests') {
225
+ when { expression { params.RUN_GCP_TESTS } }
226
+ environment {
227
+ INFRAPOOL_RUN_GCP_TESTS = 'true'
228
+ INFRAPOOL_RUBY_VERSION = '3.3'
229
+ INFRAPOOL_REGISTRY_URL = "registry.tld"
230
+ GCP_CTX_DIR = "gcp-ctx"
231
+ }
232
+ steps {
233
+ script {
234
+ // GCP agents lack a Docker runtime, so fetch a real identity token on
235
+ // the GCP VM, stash it, then run the actual test on the AWS agent with
236
+ // the token supplied via GCP_ID_TOKEN.
237
+ gcpInfrapool.agentSh "./ci/get_gcp_token.sh data/test/gcp-apps/test-app cucumber ${GCP_CTX_DIR}"
238
+ gcpInfrapool.agentStash name: 'gcp-token', includes: "${GCP_CTX_DIR}/*"
239
+ infrapool.agentUnstash name: 'gcp-token'
240
+ infrapool.agentSh """
241
+ export GCP_ID_TOKEN="\$(cat ${GCP_CTX_DIR}/token)"
242
+ export GCP_PROJECT_ID="\$(cat ${GCP_CTX_DIR}/project-id)"
243
+ ./test.sh
244
+ """
245
+ }
246
+ }
247
+ post {
248
+ always {
249
+ script { infrapool.agentStash name: 'reports-gcp', includes: '**/reports/*.xml' }
250
+ unstash 'reports-gcp'
141
251
  }
252
+ }
253
+ }
254
+
255
+ stage('Submit Coverage Report'){
256
+ steps{
257
+ // Coverage was stashed at the end of 'Test Ruby 4.0', before the cloud
258
+ // stages regenerated a partial coverage/ on the agent.
142
259
  unstash 'coverage'
143
260
 
144
261
  recordCoverage(
145
262
  tools: [[parser: 'COBERTURA', pattern: 'coverage/coverage.xml']],
146
- sourceCodeEncoding: 'ASCII',
263
+ sourceCodeEncoding: 'UTF-8',
147
264
  qualityGates: [
148
265
  [threshold: 70.0, metric: 'LINE', baseline: 'PROJECT', unstable: true],
149
266
  [threshold: 70.0, metric: 'BRANCH', baseline: 'PROJECT', unstable: true],
data/Rakefile CHANGED
@@ -23,8 +23,34 @@ begin
23
23
  require 'cucumber'
24
24
  require 'cucumber/rake/task'
25
25
 
26
+ # Cloud-authenticator features (authn-iam / authn-azure / authn-jwt gcp) are
27
+ # live integration tests that only work on the matching cloud agent, so they
28
+ # are excluded from the default suite and opted into via the CLOUD_AUTHN_TAG
29
+ # env var by the dedicated jenkins_cucumber_cloud task.
30
+ CLOUD_AUTHN_TAGS = %w[@authn_iam @authn_azure @authn_gcp].freeze
31
+
26
32
  Cucumber::Rake::Task.new(:cucumber) do |t|
27
- t.cucumber_opts = "--tags 'not @wip' --format pretty --format junit --out features/reports"
33
+ exclude = (['@wip'] + CLOUD_AUTHN_TAGS).map { |tag| "not #{tag}" }.join(' and ')
34
+ t.cucumber_opts = "--tags '#{exclude}' --format pretty --format junit --out features/reports"
35
+ end
36
+
37
+ # Runs a single cloud-authenticator feature, selected by CLOUD_AUTHN_TAG
38
+ # (e.g. CLOUD_AUTHN_TAG=@authn_iam). Used by the cloud CI stages.
39
+ #
40
+ # NOTE: this block runs at Rakefile load time, so it must not raise when
41
+ # CLOUD_AUTHN_TAG is unset (that would break every other rake invocation).
42
+ # The value is validated in the :validate_cloud_authn_tag prerequisite,
43
+ # which only runs when this task is actually invoked. The fallback tag here
44
+ # is only a placeholder for the load-time cucumber_opts string.
45
+ Cucumber::Rake::Task.new(:cucumber_cloud) do |t|
46
+ tag = ENV['CLOUD_AUTHN_TAG'].to_s.empty? ? '@authn_iam' : ENV['CLOUD_AUTHN_TAG']
47
+ t.cucumber_opts = "--tags '#{tag}' --format pretty --format junit --out features/reports"
48
+ end
49
+
50
+ task :validate_cloud_authn_tag do
51
+ if ENV['CLOUD_AUTHN_TAG'].to_s.empty?
52
+ raise 'CLOUD_AUTHN_TAG must be set (e.g. @authn_iam) to run jenkins_cucumber_cloud'
53
+ end
28
54
  end
29
55
 
30
56
  begin
@@ -33,6 +59,7 @@ begin
33
59
  task :jenkins_init => [ :init_coverage, :cuke_report_cleanup ]
34
60
  task :jenkins_spec => [ :"ci:setup:rspec", :spec ]
35
61
  task :jenkins_cucumber => [ :cucumber ]
62
+ task :jenkins_cucumber_cloud => [ :validate_cloud_authn_tag, :cucumber_cloud ]
36
63
  rescue LoadError
37
64
  warn "ci_reporter_rspec not found, jenkins task will be unavailable"
38
65
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.3.1
1
+ 6.4.0-849
@@ -0,0 +1,43 @@
1
+ #!/bin/bash -e
2
+ #
3
+ # Fetch a GCP identity token from the instance metadata service.
4
+ #
5
+ # This is meant to run ON a GCP executor agent. GCP agents typically lack a
6
+ # Docker runtime, so the authn-jwt/gcp integration test itself runs on a
7
+ # different (e.g. AWS) agent. This script fetches a real identity token here,
8
+ # writes it to a file, and the Jenkins pipeline stashes it and hands it to the
9
+ # test container via the GCP_ID_TOKEN env var.
10
+ #
11
+ # Usage: ./ci/get_gcp_token.sh <host-id> <account> <output-dir>
12
+ # host-id Conjur host id WITHOUT the "host/" prefix
13
+ # (e.g. "data/test/gcp-apps/test-app")
14
+ # account Conjur account (e.g. "cucumber")
15
+ # output-dir directory to write "token" and "project-id" into
16
+
17
+ HOST_ID="${1:?host-id (without host/ prefix) is required}"
18
+ ACCOUNT="${2:?account is required}"
19
+ OUT_DIR="${3:?output directory is required}"
20
+
21
+ METADATA_BASE="http://metadata.google.internal/computeMetadata/v1"
22
+ AUDIENCE="conjur/${ACCOUNT}/host/${HOST_ID}"
23
+
24
+ mkdir -p "${OUT_DIR}"
25
+
26
+ echo "Fetching GCP identity token for audience: ${AUDIENCE}"
27
+ token="$(curl -sS --noproxy '*' -H 'Metadata-Flavor: Google' \
28
+ "${METADATA_BASE}/instance/service-accounts/default/identity?audience=${AUDIENCE}&format=full")"
29
+
30
+ # Sanity-check that we got a 3-part JWT rather than an HTML error page.
31
+ if [[ "$(grep -o '\.' <<< "${token}" | wc -l | tr -d ' ')" != "2" ]]; then
32
+ echo "ERROR: metadata service did not return a JWT. Got:" >&2
33
+ echo "${token}" >&2
34
+ exit 1
35
+ fi
36
+
37
+ project_id="$(curl -sS --noproxy '*' -H 'Metadata-Flavor: Google' \
38
+ "${METADATA_BASE}/project/project-id")"
39
+
40
+ printf '%s' "${token}" > "${OUT_DIR}/token"
41
+ printf '%s' "${project_id}" > "${OUT_DIR}/project-id"
42
+
43
+ echo "Wrote ${OUT_DIR}/token and ${OUT_DIR}/project-id"
data/conjur-api.gemspec CHANGED
@@ -40,4 +40,8 @@ Gem::Specification.new do |gem|
40
40
  gem.add_development_dependency 'pry-byebug'
41
41
  gem.add_development_dependency 'nokogiri'
42
42
  gem.add_development_dependency 'webrick'
43
+ # Optional runtime dependency for the IAM authenticator (authn-iam); not a
44
+ # hard dependency of the gem, but required to exercise it in the integration
45
+ # tests.
46
+ gem.add_development_dependency 'aws-sdk-core'
43
47
  end
data/docker-compose.yml CHANGED
@@ -11,6 +11,21 @@ services:
11
11
  environment:
12
12
  DATABASE_URL: postgres://postgres@pg/postgres
13
13
  CONJUR_DATA_KEY: 'WMfApcDBtocRWV+ZSUP3Tjr5XNU+Z2FdBb6BEezejIs='
14
+ # NOTE: CONJUR_AUTHENTICATORS is intentionally left unset. When unset the
15
+ # server is permissive, so both the existing authn-oidc tests and the
16
+ # cloud-authenticator features can enable their authenticators at runtime
17
+ # via authenticator_enable. Setting an explicit allowlist here makes the
18
+ # server strict and breaks the authn-oidc scenarios, and any authenticators
19
+ # listed would show up in authenticator_list's "enabled" set (which the
20
+ # authenticators.feature assertions depend on staying ["authn"]).
21
+ #
22
+ # Conjur's SSRF guard blocks authenticator fetches (authn-oidc provider-uri here) that
23
+ # resolve to loopback/RFC1918 ranges or use plain HTTP. Keycloak is reached by compose
24
+ # service name, so it resolves inside the bridge network's private subnet and its
25
+ # discovery document advertises http://keycloak:8080 endpoints. Both guards must be
26
+ # opted out of for this test env. Mirrors conjur's own ci/docker-compose.yml.
27
+ CONJUR_AUTHN_ALLOWED_IP_RANGES: ${CONJUR_AUTHN_ALLOWED_IP_RANGES:-0.0.0.0/0,::/0}
28
+ CONJUR_AUTHN_HTTP_ALLOWLIST: ${CONJUR_AUTHN_HTTP_ALLOWLIST:-keycloak}
14
29
  volumes:
15
30
  - authn_local:/run/authn-local
16
31
  - ./ci/oauth/keycloak:/scripts
@@ -0,0 +1,80 @@
1
+ # Cucumber integration features
2
+
3
+ The default suite (run by `rake jenkins_cucumber` via `test.sh`) exercises the
4
+ API against a local Conjur brought up by `docker-compose`.
5
+
6
+ ## Cloud authenticator features
7
+
8
+ `authn_iam.feature`, `authn_azure.feature`, and `authn_gcp.feature` are **live
9
+ integration tests** for the AWS IAM / Azure / GCP authenticators. They are
10
+ tagged `@authn_iam` / `@authn_azure` / `@authn_gcp` and are **excluded from the
11
+ default run** (see `Rakefile`), because they only pass on the matching cloud
12
+ provider's infrastructure.
13
+
14
+ ### How they run in CI
15
+
16
+ They are gated behind Jenkins boolean params `RUN_AWS_TESTS` / `RUN_AZURE_TESTS`
17
+ / `RUN_GCP_TESTS` (default `false`; enabled by the daily cron). When a flag is
18
+ set, `test.sh` runs only that authenticator's feature against a real Conjur
19
+ (with `CONJUR_AUTHENTICATORS` including the cloud authenticators) plus the real
20
+ cloud metadata/STS service:
21
+
22
+ - **AWS IAM** — runs on the standard `ExecutorV2` agent, which is itself in AWS
23
+ and carries an instance profile. The stage derives the caller identity with
24
+ `aws sts get-caller-identity` and passes `AWS_ACCOUNT_ID` / `AWS_ROLE_NAME`;
25
+ the Conjur host id is `<account-id>/<role-name>` (authn-iam matches the host
26
+ to the signing role). `new_from_iam` signs a real STS `GetCallerIdentity`
27
+ request.
28
+ - **Azure** — runs on an `AzureExecutorV2` agent. `summon -e azure` injects
29
+ `AZURE_SUBSCRIPTION_ID`, `AZURE_RESOURCE_GROUP`, and
30
+ `USER_ASSIGNED_IDENTITY_CLIENT_ID`. `new_from_azure` fetches a JWT from the
31
+ real Azure IMDS (`169.254.169.254`); the feature derives the `provider-uri`
32
+ from that token's `iss` claim and annotates the host with the
33
+ subscription-id / resource-group restrictions.
34
+ - **GCP** — GCP agents lack a Docker runtime, so the flow is split:
35
+ `ci/get_gcp_token.sh` fetches a real identity token on a `GcpExecutorV2`
36
+ agent, Jenkins stashes it, and the test runs on the AWS agent with the token
37
+ passed via `GCP_ID_TOKEN` (and `GCP_PROJECT_ID` for the host's
38
+ `authn-gcp/project-id` annotation). `new_from_gcp` exchanges that pre-fetched
39
+ token via the serviceless `authn-gcp` endpoint, bypassing the metadata service.
40
+
41
+ `test.sh` forwards the cloud env vars into the tester container and clears
42
+ `HTTP(S)_PROXY` / sets `NO_PROXY` so the link-local metadata endpoints are
43
+ reachable directly.
44
+
45
+ ### Running one locally
46
+
47
+ You must be on (or have credentials for) the relevant cloud. For example, on an
48
+ AWS host with an appropriate instance profile / credentials:
49
+
50
+ ```sh
51
+ RUN_AWS_TESTS=true ./test.sh
52
+ ```
53
+
54
+ For GCP, fetch a token first (on a GCP host), then point the test at it:
55
+
56
+ ```sh
57
+ ./ci/get_gcp_token.sh data/test/gcp-apps/test-app cucumber gcp-ctx
58
+ GCP_ID_TOKEN="$(cat gcp-ctx/token)" RUN_GCP_TESTS=true ./test.sh
59
+ ```
60
+
61
+ ### CI infrastructure these depend on
62
+
63
+ The cloud stages authenticate against real provider infrastructure, so they
64
+ rely on the following being in place (all currently provisioned in the
65
+ Conjur-Enterprise CI):
66
+
67
+ 1. `AzureExecutorV2` / `GcpExecutorV2` InfraPool agent types, plus the standard
68
+ AWS `ExecutorV2` agent (used for AWS IAM and to run the GCP test).
69
+ 2. The `!var` references in `secrets.yml` (`ci/azure/...`) resolvable from the
70
+ CI Conjur vault, so `summon -e azure` can supply the `AZURE_*` /
71
+ `USER_ASSIGNED_IDENTITY_CLIENT_ID` values.
72
+ 3. Cloud identities matching how each feature builds its Conjur host:
73
+ - **AWS**: the executor's instance-profile role (host id `<account>/<role>`,
74
+ derived at runtime).
75
+ - **Azure**: the managed identity in the given subscription / resource-group.
76
+ - **GCP**: the `GcpExecutorV2` agent's service account, mapped to
77
+ `data/test/gcp-apps/test-app` via the `authn-gcp/project-id` annotation.
78
+
79
+ If any of these change, a stage may fail with an opaque `401`; `test.sh` dumps
80
+ the Conjur server log on cloud-test failure so the precise reason is visible.
@@ -0,0 +1,15 @@
1
+ @authn_azure
2
+ Feature: Authenticate to Conjur using Azure Managed Identity (authn-azure)
3
+
4
+ This scenario is a live integration test: it exercises new_from_azure against
5
+ the real Azure Instance Metadata Service (IMDS). It is tagged @authn_azure and
6
+ is only run in CI on an Azure executor agent, gated by RUN_AZURE_TESTS, which
7
+ also supplies AZURE_SUBSCRIPTION_ID / AZURE_RESOURCE_GROUP / the user-assigned
8
+ identity client id. See Jenkinsfile and features/README.md.
9
+
10
+ Scenario: Authenticate with a system-assigned identity and retrieve a secret
11
+ Given I enable the authn-azure authenticator "prod" for host "test/azure-apps/test-app" using Azure environment config
12
+ And the secret "test/azure-apps/db-password" has value "s3cr3t"
13
+ When I authenticate via authn-azure as "host/test/azure-apps/test-app" with service id "prod"
14
+ Then I receive a Conjur access token
15
+ And I can retrieve the secret "test/azure-apps/db-password" with value "s3cr3t"
@@ -0,0 +1,19 @@
1
+ @authn_gcp
2
+ Feature: Authenticate to Conjur using GCP workload identity (authn-gcp)
3
+
4
+ This scenario is a live integration test. GCP executor agents typically lack a
5
+ Docker runtime, so the flow is split: ci/get_gcp_token.sh fetches a real GCP
6
+ identity token on the GCP agent, the pipeline hands it to this test via
7
+ GCP_ID_TOKEN, and new_from_gcp exchanges that pre-fetched token with Conjur
8
+ (bypassing the metadata service). Tagged @authn_gcp, gated by RUN_GCP_TESTS.
9
+ See Jenkinsfile and features/README.md.
10
+
11
+ authn-gcp is a serviceless authenticator: the host is identified by its
12
+ authn-gcp/project-id annotation (set from GCP_PROJECT_ID), not a service id.
13
+
14
+ Scenario: Authenticate with a pre-fetched GCP identity token and retrieve a secret
15
+ Given I enable the authn-gcp authenticator for host "data/test/gcp-apps/test-app"
16
+ And the secret "data/test/gcp-apps/db-password" has value "s3cr3t"
17
+ When I authenticate via authn-gcp as "host/data/test/gcp-apps/test-app" using the pre-fetched token
18
+ Then I receive a Conjur access token
19
+ And I can retrieve the secret "data/test/gcp-apps/db-password" with value "s3cr3t"
@@ -0,0 +1,18 @@
1
+ @authn_iam
2
+ Feature: Authenticate to Conjur using AWS IAM (authn-iam)
3
+
4
+ This scenario is a live integration test: it exercises new_from_iam against
5
+ the real AWS STS service using the credentials of the host it runs on. It is
6
+ tagged @authn_iam and is only run in CI on an AWS executor agent, gated by
7
+ RUN_AWS_TESTS. See Jenkinsfile and features/README.md.
8
+
9
+ authn-iam identifies the Conjur host by the AWS caller identity, so the host
10
+ id is "<AWS_ACCOUNT_ID>/<AWS_ROLE_NAME>" (derived on the agent from
11
+ `aws sts get-caller-identity` and passed in via env).
12
+
13
+ Scenario: Authenticate and retrieve a secret via authn-iam
14
+ Given I enable the authn-iam authenticator "prod" for the AWS caller identity host
15
+ And the secret "test/iam-apps/db-password" has value "s3cr3t"
16
+ When I authenticate via authn-iam as the AWS caller identity host with service id "prod"
17
+ Then I receive a Conjur access token
18
+ And I can retrieve the secret "test/iam-apps/db-password" with value "s3cr3t"
@@ -0,0 +1,182 @@
1
+ require 'base64'
2
+ require 'json'
3
+
4
+ # Step definitions for the cloud authenticator integration features
5
+ # (authn-iam, authn-azure, authn-gcp).
6
+ #
7
+ # These steps drive real Conjur (brought up by docker-compose) against the
8
+ # cloud provider's real metadata/STS services. They are only meaningful when
9
+ # run on the matching cloud agent, so the features carry @authn_iam /
10
+ # @authn_azure / @authn_gcp tags and are gated by RUN_AWS_TESTS /
11
+ # RUN_AZURE_TESTS / RUN_GCP_TESTS in the pipeline.
12
+
13
+ # --- authn-iam -------------------------------------------------------------
14
+
15
+ # authn-iam matches the Conjur host to the AWS caller identity, so the host id
16
+ # must be "<account-id>/<role-name>" (supplied via env by the AWS CI stage).
17
+ def aws_caller_identity_host_id
18
+ account = ENV.fetch('AWS_ACCOUNT_ID')
19
+ role = ENV.fetch('AWS_ROLE_NAME')
20
+ "#{account}/#{role}"
21
+ end
22
+
23
+ Given(/^I enable the authn-iam authenticator "([^"]+)" for the AWS caller identity host$/) do |service_id|
24
+ host_id = aws_caller_identity_host_id
25
+ $conjur.load_policy 'root', <<-POLICY
26
+ - !policy
27
+ id: conjur/authn-iam/#{service_id}
28
+ body:
29
+ - !webservice
30
+ - !group apps
31
+ - !permit
32
+ role: !group apps
33
+ privilege: [ read, authenticate ]
34
+ resource: !webservice
35
+
36
+ - !host
37
+ id: #{host_id}
38
+ annotations:
39
+ authn-iam/#{service_id}: true
40
+
41
+ - !grant
42
+ role: !group conjur/authn-iam/#{service_id}/apps
43
+ member: !host #{host_id}
44
+ POLICY
45
+
46
+ @cloud_host_id = host_id
47
+ $conjur.authenticator_enable('authn-iam', service_id)
48
+ end
49
+
50
+ When(/^I authenticate via authn-iam as the AWS caller identity host with service id "([^"]+)"$/) do |service_id|
51
+ identity = "host/#{aws_caller_identity_host_id}"
52
+ @cloud_api = Conjur::API.new_from_iam(identity, service_id: service_id)
53
+ @result = @cloud_api.token
54
+ end
55
+
56
+ # --- authn-azure -----------------------------------------------------------
57
+
58
+ # The authn-azure provider-uri must equal the token's actual issuer
59
+ # (https://sts.windows.net/<tenant>/). Derive it from a real IMDS token's `iss`
60
+ # claim rather than hardcoding, matching conjur-api-java's resolveProviderUriFromImds.
61
+ def azure_provider_uri
62
+ jwt = Conjur::API.send(:fetch_azure_jwt,
63
+ resource_uri: Conjur::API::AzureAuthenticator::DEFAULT_RESOURCE,
64
+ client_id: (ENV['USER_ASSIGNED_IDENTITY_CLIENT_ID'] unless ENV['USER_ASSIGNED_IDENTITY_CLIENT_ID'].to_s.empty?),
65
+ imds_base_url: Conjur::API::AzureAuthenticator::DEFAULT_IMDS_BASE,
66
+ imds_api_version: Conjur::API::AzureAuthenticator::DEFAULT_IMDS_VER)
67
+ payload = JSON.parse(Base64.decode64(jwt.split('.')[1]))
68
+ issuer = payload.fetch('iss')
69
+ issuer.end_with?('/') ? issuer : "#{issuer}/"
70
+ end
71
+
72
+ Given(/^I enable the authn-azure authenticator "([^"]+)" for host "([^"]+)" using Azure environment config$/) do |service_id, host_id|
73
+ subscription = ENV.fetch('AZURE_SUBSCRIPTION_ID')
74
+ resource_group = ENV.fetch('AZURE_RESOURCE_GROUP')
75
+ provider_uri = azure_provider_uri
76
+
77
+ $conjur.load_policy 'root', <<-POLICY
78
+ - !policy
79
+ id: conjur/authn-azure/#{service_id}
80
+ body:
81
+ - !webservice
82
+ - !variable provider-uri
83
+ - !group apps
84
+ - !permit
85
+ role: !group apps
86
+ privilege: [ read, authenticate ]
87
+ resource: !webservice
88
+
89
+ - !host
90
+ id: #{host_id}
91
+ annotations:
92
+ authn-azure/subscription-id: "#{subscription}"
93
+ authn-azure/resource-group: "#{resource_group}"
94
+
95
+ - !grant
96
+ role: !group conjur/authn-azure/#{service_id}/apps
97
+ member: !host #{host_id}
98
+ POLICY
99
+
100
+ @cloud_host_id = host_id
101
+ $conjur.resource("cucumber:variable:conjur/authn-azure/#{service_id}/provider-uri")
102
+ .add_value(provider_uri)
103
+ $conjur.authenticator_enable('authn-azure', service_id)
104
+ end
105
+
106
+ When(/^I authenticate via authn-azure as "([^"]+)" with service id "([^"]+)"(?: and client id "([^"]*)")?$/) do |identity, service_id, client_id|
107
+ # new_from_azure takes service_id and identity positionally; client_id is a keyword.
108
+ opts = {}
109
+ opts[:client_id] = client_id if client_id && !client_id.empty?
110
+ @cloud_api = Conjur::API.new_from_azure(service_id, identity, **opts)
111
+ @result = @cloud_api.token
112
+ end
113
+
114
+ # --- authn-gcp -------------------------------------------------------------
115
+
116
+ Given(/^I enable the authn-gcp authenticator for host "([^"]+)"$/) do |host_id|
117
+ # authn-gcp is serviceless. The host is matched to the GCP identity token via
118
+ # its authn-gcp/project-id annotation, so the project id must be the one the
119
+ # token was issued for (supplied by ci/get_gcp_token.sh as GCP_PROJECT_ID).
120
+ project_id = ENV.fetch('GCP_PROJECT_ID')
121
+
122
+ $conjur.load_policy 'root', <<-POLICY
123
+ - !policy
124
+ id: conjur/authn-gcp
125
+ body:
126
+ - !webservice
127
+ - !group apps
128
+ - !permit
129
+ role: !group apps
130
+ privilege: [ read, authenticate ]
131
+ resource: !webservice
132
+
133
+ - !host
134
+ id: #{host_id}
135
+ annotations:
136
+ authn-gcp/project-id: "#{project_id}"
137
+
138
+ - !grant
139
+ role: !group conjur/authn-gcp/apps
140
+ member: !host #{host_id}
141
+ POLICY
142
+
143
+ @cloud_host_id = host_id
144
+ $conjur.authenticator_enable('authn-gcp', '')
145
+ end
146
+
147
+ When(/^I authenticate via authn-gcp as "([^"]+)" using the pre-fetched token$/) do |identity|
148
+ jwt = ENV['GCP_ID_TOKEN']
149
+ raise 'GCP_ID_TOKEN is not set; run ci/get_gcp_token.sh on a GCP agent first' if jwt.nil? || jwt.empty?
150
+
151
+ @cloud_api = Conjur::API.new_from_gcp(identity, jwt: jwt)
152
+ @result = @cloud_api.token
153
+ end
154
+
155
+ # --- shared assertions -----------------------------------------------------
156
+
157
+ Then(/^I receive a Conjur access token$/) do
158
+ # Conjur access tokens are returned parsed into a Hash of the JWT parts.
159
+ expect(@result).to be_a(Hash)
160
+ expect(@result).to include('protected', 'payload', 'signature')
161
+ end
162
+
163
+ Given(/^the secret "([^"]+)" has value "([^"]+)"$/) do |var_id, value|
164
+ # Declare the variable and grant the cloud host permission to read it, then
165
+ # set the value. @cloud_host_id is captured by the "enable ... authenticator"
166
+ # step that always runs first.
167
+ raise 'no cloud host configured; run the "enable authenticator" step first' unless @cloud_host_id
168
+
169
+ $conjur.load_policy 'root', <<-POLICY
170
+ - !variable #{var_id}
171
+ - !permit
172
+ role: !host #{@cloud_host_id}
173
+ privilege: [ read, execute ]
174
+ resource: !variable #{var_id}
175
+ POLICY
176
+
177
+ $conjur.resource("cucumber:variable:#{var_id}").add_value(value)
178
+ end
179
+
180
+ Then(/^I can retrieve the secret "([^"]+)" with value "([^"]+)"$/) do |var_id, value|
181
+ expect(@cloud_api.resource("cucumber:variable:#{var_id}").value).to eq(value)
182
+ end