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 +4 -4
- data/CHANGELOG.md +27 -9
- data/Jenkinsfile +123 -6
- data/Rakefile +28 -1
- data/VERSION +1 -1
- data/ci/get_gcp_token.sh +43 -0
- data/conjur-api.gemspec +4 -0
- data/docker-compose.yml +15 -0
- data/features/README.md +80 -0
- data/features/authn_azure.feature +15 -0
- data/features/authn_gcp.feature +19 -0
- data/features/authn_iam.feature +18 -0
- data/features/step_definitions/cloud_authn_steps.rb +182 -0
- data/lib/conjur/api/authn_azure.rb +145 -0
- data/lib/conjur/api/authn_gcp.rb +137 -0
- data/lib/conjur/api/authn_iam.rb +140 -0
- data/lib/conjur/api/router.rb +33 -2
- data/lib/conjur/api.rb +3 -0
- data/secrets.yml +13 -0
- data/spec/authn_azure_spec.rb +91 -0
- data/spec/authn_gcp_spec.rb +121 -0
- data/spec/authn_iam_spec.rb +103 -0
- data/test.sh +78 -0
- metadata +36 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1cdd341c320a370b2fe2a1acc7897da7cf46646b0f2e753c4a36210b9bedad5
|
|
4
|
+
data.tar.gz: 98e3471100dfd37156b37f1f60d8ff6dfb730879e3ad1f919cca1334a429a295
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
34
|
-
|
|
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('
|
|
138
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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.
|
|
1
|
+
6.4.0-849
|
data/ci/get_gcp_token.sh
ADDED
|
@@ -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
|
data/features/README.md
ADDED
|
@@ -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
|