conjur-api 6.3.1.pre.846 → 6.3.1.pre.848
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 +7 -1
- 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 +8 -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_gcp.rb +33 -49
- data/lib/conjur/api/router.rb +12 -5
- data/secrets.yml +13 -0
- data/spec/authn_gcp_spec.rb +13 -68
- data/test.sh +78 -0
- metadata +27 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e94af8f767fd0324c87edb5648af755334c349670c1b4d50a2b4cec6927eb2f5
|
|
4
|
+
data.tar.gz: e5a01b8576510b3f21209b2a75790ee6f37607c011a8d0937481386e78d0746f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74c14463069c05daaa17c1c9aca5ffec2cc884835762c4a0bffaa0322158a3c09145badf7257b8bfdb19888b98b6298e548973111aa4463465da6f584621b95b
|
|
7
|
+
data.tar.gz: 6ed18e8e9acb13891834d729cd1fc5a4b678942b548be8418c58480517b90b0b28870a0a966dd176e2302e41e8026880d77acbfebd9eb027c0cfb7c9e0fb280d
|
data/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
### Added
|
|
9
9
|
- Cloud-native authenticators for Azure Managed Identity (`authn-azure`),
|
|
10
|
-
AWS IAM (`authn-iam`), and GCP workload identity (`authn-
|
|
10
|
+
AWS IAM (`authn-iam`), and GCP workload identity (`authn-gcp`), exposed
|
|
11
11
|
via `Conjur::API.new_from_azure`, `.new_from_iam`, and `.new_from_gcp`
|
|
12
12
|
(CNJR-14213).
|
|
13
|
+
- Live integration features for the cloud authenticators, gated behind the
|
|
14
|
+
`RUN_AWS_TESTS` / `RUN_AZURE_TESTS` / `RUN_GCP_TESTS` pipeline params and run
|
|
15
|
+
on dedicated cloud agents (excluded from the default suite) (CNJR-14213).
|
|
13
16
|
|
|
14
17
|
### Changed
|
|
15
18
|
- CI uses Conjur Enterprise 1.27.0+, which removed the `GET /public_keys` endpoint
|
|
@@ -24,6 +27,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
24
27
|
- Set `CONJUR_AUTHN_ALLOWED_IP_RANGES` and `CONJUR_AUTHN_HTTP_ALLOWLIST` in the
|
|
25
28
|
test environment so Conjur's new SSRF guard does not block the authn-oidc
|
|
26
29
|
`provider-uri` fetch to the compose-local Keycloak server. (CNJR-13786)
|
|
30
|
+
- Corrected `Conjur::API.new_from_gcp` / `authenticate_gcp` to target the
|
|
31
|
+
serviceless `authn-gcp` endpoint (`/authn-gcp/{account}/authenticate`) instead
|
|
32
|
+
of `authn-jwt`; the `service_id` argument was removed accordingly (CNJR-14213).
|
|
27
33
|
|
|
28
34
|
## [6.3.1] - 2026-07-16
|
|
29
35
|
|
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.3.1-
|
|
1
|
+
6.3.1-848
|
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,14 @@ 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
|
+
#
|
|
14
22
|
# Conjur's SSRF guard blocks authenticator fetches (authn-oidc provider-uri here) that
|
|
15
23
|
# resolve to loopback/RFC1918 ranges or use plain HTTP. Keycloak is reached by compose
|
|
16
24
|
# service name, so it resolves inside the bridge network's private subnet and its
|
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
|
data/lib/conjur/api/authn_gcp.rb
CHANGED
|
@@ -9,94 +9,82 @@ module Conjur
|
|
|
9
9
|
class API
|
|
10
10
|
# Authenticator that uses a GCP identity token to obtain Conjur access tokens.
|
|
11
11
|
# Fetches an identity JWT from the GCP Instance Metadata Service, then POSTs it
|
|
12
|
-
# to the Conjur authn-
|
|
12
|
+
# to the Conjur authn-gcp endpoint.
|
|
13
|
+
#
|
|
14
|
+
# authn-gcp is a serviceless authenticator: there is no service ID, and Conjur
|
|
15
|
+
# identifies the calling host from the token's claims (matched against the
|
|
16
|
+
# host's authn-gcp/project-id annotation), so the host id is not sent in the
|
|
17
|
+
# authenticate URL.
|
|
13
18
|
#
|
|
14
19
|
# An explicit JWT may be supplied to bypass the metadata service fetch —
|
|
15
20
|
# useful in CI environments where the token is pre-fetched on a GCP agent.
|
|
16
21
|
class GCPAuthenticator
|
|
17
22
|
include TokenExpiration
|
|
18
23
|
|
|
19
|
-
DEFAULT_SERVICE_ID = 'gcp'.freeze
|
|
20
24
|
DEFAULT_METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1' \
|
|
21
25
|
'/instance/service-accounts/default/identity'.freeze
|
|
22
26
|
|
|
23
|
-
attr_reader :account, :
|
|
27
|
+
attr_reader :account, :identity, :jwt, :gcp_identity_url
|
|
24
28
|
|
|
25
|
-
def initialize(account,
|
|
26
|
-
gcp_identity_url: DEFAULT_METADATA_URL
|
|
29
|
+
def initialize(account, identity, jwt: nil,
|
|
30
|
+
gcp_identity_url: DEFAULT_METADATA_URL)
|
|
27
31
|
@account = account
|
|
28
|
-
@service_id = service_id
|
|
29
32
|
@identity = identity
|
|
30
33
|
@jwt = jwt
|
|
31
34
|
@gcp_identity_url = gcp_identity_url
|
|
32
|
-
@audience = audience
|
|
33
35
|
update_token_born
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def refresh_token
|
|
37
39
|
Conjur::API.authenticate_gcp(identity, account: account,
|
|
38
|
-
service_id: service_id,
|
|
39
40
|
jwt: jwt,
|
|
40
|
-
gcp_identity_url: gcp_identity_url
|
|
41
|
-
audience: audience).tap do
|
|
41
|
+
gcp_identity_url: gcp_identity_url).tap do
|
|
42
42
|
update_token_born
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
class << self
|
|
48
|
-
# Create a {Conjur::API} instance authenticated via GCP workload identity (authn-
|
|
48
|
+
# Create a {Conjur::API} instance authenticated via GCP workload identity (authn-gcp).
|
|
49
49
|
#
|
|
50
|
-
# @param [String] identity Conjur host identity (e.g. "host/data/test/gcp-apps/test-app")
|
|
51
|
-
#
|
|
50
|
+
# @param [String] identity Conjur host identity (e.g. "host/data/test/gcp-apps/test-app").
|
|
51
|
+
# Used to build the metadata-service audience; Conjur itself derives the
|
|
52
|
+
# host from the token, so it is not sent in the authenticate request.
|
|
52
53
|
# @param [String] account the Conjur organization account
|
|
53
54
|
# @param [String, nil] jwt pre-obtained GCP identity token; when supplied the metadata
|
|
54
55
|
# service is not called. Useful in tests and CI where the token is pre-fetched.
|
|
55
56
|
# @param [String] gcp_identity_url override for the GCP metadata identity URL (for tests)
|
|
56
|
-
# @param [String, nil] audience the audience claim requested from the metadata service.
|
|
57
|
-
# Must match the audience configured in the authn-jwt/gcp policy. Defaults to
|
|
58
|
-
# "conjur/{account}/host/{identity}" when not supplied.
|
|
59
57
|
# @param [String, nil] remote_ip optional IP address recorded in the audit log
|
|
60
58
|
# @return [Conjur::API]
|
|
61
59
|
def new_from_gcp(identity,
|
|
62
60
|
account: Conjur.configuration.account,
|
|
63
|
-
service_id: GCPAuthenticator::DEFAULT_SERVICE_ID,
|
|
64
61
|
jwt: nil,
|
|
65
62
|
gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL,
|
|
66
|
-
audience: nil,
|
|
67
63
|
remote_ip: nil)
|
|
68
|
-
self.new.init_from_gcp(identity, account: account,
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
self.new.init_from_gcp(identity, account: account,
|
|
65
|
+
jwt: jwt, gcp_identity_url: gcp_identity_url,
|
|
66
|
+
remote_ip: remote_ip)
|
|
71
67
|
end
|
|
72
68
|
|
|
73
|
-
# Authenticate via authn-
|
|
69
|
+
# Authenticate via authn-gcp and return a parsed Conjur access token.
|
|
74
70
|
#
|
|
75
|
-
# @param [String] identity Conjur host identity
|
|
76
|
-
# @param [String] service_id the authn-jwt service ID
|
|
71
|
+
# @param [String] identity Conjur host identity (used to build the audience)
|
|
77
72
|
# @param [String] account the Conjur organization account
|
|
78
73
|
# @param [String, nil] jwt pre-obtained GCP identity token
|
|
79
74
|
# @param [String] gcp_identity_url override for the GCP metadata identity URL
|
|
80
|
-
# @param [String, nil] audience the audience claim requested from the metadata service.
|
|
81
|
-
# Must match the audience configured in the authn-jwt/gcp policy. Defaults to
|
|
82
|
-
# "conjur/{account}/host/{identity}" when not supplied.
|
|
83
75
|
# @return [Hash] parsed access token
|
|
84
76
|
def authenticate_gcp(identity,
|
|
85
77
|
account: Conjur.configuration.account,
|
|
86
|
-
service_id: GCPAuthenticator::DEFAULT_SERVICE_ID,
|
|
87
78
|
jwt: nil,
|
|
88
|
-
gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL
|
|
89
|
-
|
|
90
|
-
raise ArgumentError, "identity is required" if identity.nil? || identity.empty?
|
|
91
|
-
raise ArgumentError, "service_id is required" if service_id.nil? || service_id.empty?
|
|
79
|
+
gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL)
|
|
80
|
+
raise ArgumentError, "identity is required" if identity.nil? || identity.empty?
|
|
92
81
|
|
|
93
82
|
if Conjur.log
|
|
94
|
-
Conjur.log << "Authenticating #{identity} to account #{account} via authn-
|
|
83
|
+
Conjur.log << "Authenticating #{identity} to account #{account} via authn-gcp\n"
|
|
95
84
|
end
|
|
96
85
|
|
|
97
|
-
token = jwt || fetch_gcp_jwt(account, identity,
|
|
98
|
-
|
|
99
|
-
JSON.parse(url_for(:authn_gcp_authenticate, account, service_id, identity)
|
|
86
|
+
token = jwt || fetch_gcp_jwt(account, identity, gcp_identity_url: gcp_identity_url)
|
|
87
|
+
JSON.parse(url_for(:authn_gcp_authenticate, account)
|
|
100
88
|
.post("jwt=#{CGI.escape(token)}", content_type: 'application/x-www-form-urlencoded'))
|
|
101
89
|
end
|
|
102
90
|
|
|
@@ -106,11 +94,10 @@ module Conjur
|
|
|
106
94
|
# Uses a direct Net::HTTP connection (proxy addr = nil) because the metadata
|
|
107
95
|
# link-local address must never be routed through a proxy.
|
|
108
96
|
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
aud = audience || default_gcp_audience(account, identity)
|
|
97
|
+
# The requested audience is "conjur/{account}/host/{identity}", matching what
|
|
98
|
+
# the authn-gcp authenticator expects.
|
|
99
|
+
def fetch_gcp_jwt(account, identity, gcp_identity_url:)
|
|
100
|
+
aud = gcp_audience(account, identity)
|
|
114
101
|
url = "#{gcp_identity_url}?#{URI.encode_www_form(audience: aud, format: 'full')}"
|
|
115
102
|
|
|
116
103
|
uri = URI(url)
|
|
@@ -127,10 +114,10 @@ module Conjur
|
|
|
127
114
|
token
|
|
128
115
|
end
|
|
129
116
|
|
|
130
|
-
# Builds the
|
|
131
|
-
#
|
|
117
|
+
# Builds the authn-gcp audience for a Conjur host identity, e.g.
|
|
118
|
+
# "conjur/myorg/host/data/app". The "host/" prefix is optional in the
|
|
132
119
|
# supplied identity and is normalized away before being re-applied.
|
|
133
|
-
def
|
|
120
|
+
def gcp_audience(account, identity)
|
|
134
121
|
normalized = identity.start_with?('host/') ? identity['host/'.length..] : identity
|
|
135
122
|
"conjur/#{account}/host/#{normalized}"
|
|
136
123
|
end
|
|
@@ -138,15 +125,12 @@ module Conjur
|
|
|
138
125
|
|
|
139
126
|
def init_from_gcp(identity,
|
|
140
127
|
account: Conjur.configuration.account,
|
|
141
|
-
service_id: GCPAuthenticator::DEFAULT_SERVICE_ID,
|
|
142
128
|
jwt: nil,
|
|
143
129
|
gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL,
|
|
144
|
-
audience: nil,
|
|
145
130
|
remote_ip: nil)
|
|
146
131
|
@remote_ip = remote_ip
|
|
147
|
-
@authenticator = GCPAuthenticator.new(account,
|
|
148
|
-
jwt: jwt, gcp_identity_url: gcp_identity_url
|
|
149
|
-
audience: audience)
|
|
132
|
+
@authenticator = GCPAuthenticator.new(account, identity,
|
|
133
|
+
jwt: jwt, gcp_identity_url: gcp_identity_url)
|
|
150
134
|
self
|
|
151
135
|
end
|
|
152
136
|
end
|
data/lib/conjur/api/router.rb
CHANGED
|
@@ -58,12 +58,15 @@ module Conjur
|
|
|
58
58
|
)['authn-iam'][fully_escape service_id][fully_escape account][fully_escape identity]['authenticate']
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
# POST /authn-
|
|
62
|
-
|
|
61
|
+
# POST /authn-gcp/{account}/authenticate
|
|
62
|
+
# authn-gcp is a serviceless authenticator: Conjur derives the host from
|
|
63
|
+
# the GCP identity token's claims, so neither a service id nor the login
|
|
64
|
+
# appears in the path.
|
|
65
|
+
def authn_gcp_authenticate account
|
|
63
66
|
RestClient::Resource.new(
|
|
64
67
|
Conjur.configuration.core_url,
|
|
65
68
|
Conjur.configuration.rest_client_options
|
|
66
|
-
)['authn-
|
|
69
|
+
)['authn-gcp'][fully_escape account]['authenticate']
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
# Builds the RestClient::Resource for an authn-cert authentication request.
|
|
@@ -86,10 +89,14 @@ module Conjur
|
|
|
86
89
|
end
|
|
87
90
|
|
|
88
91
|
def authenticator account, authenticator, service_id, credentials
|
|
89
|
-
RestClient::Resource.new(
|
|
92
|
+
resource = RestClient::Resource.new(
|
|
90
93
|
Conjur.configuration.core_url,
|
|
91
94
|
Conjur.configuration.create_rest_client_options(credentials)
|
|
92
|
-
)[fully_escape authenticator]
|
|
95
|
+
)[fully_escape authenticator]
|
|
96
|
+
# Serviceless authenticators (e.g. authn-gcp) have no service id segment:
|
|
97
|
+
# the path is authn-<type>/<account>.
|
|
98
|
+
resource = resource[fully_escape service_id] unless service_id.nil? || service_id.empty?
|
|
99
|
+
resource[fully_escape account]
|
|
93
100
|
end
|
|
94
101
|
|
|
95
102
|
def authenticators
|
data/secrets.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Summon secrets for CI. `summon -e <env> ./test.sh` resolves the !var
|
|
2
|
+
# references below from the CI Conjur vault and exports them into the
|
|
3
|
+
# environment for the wrapped command.
|
|
4
|
+
#
|
|
5
|
+
# The azure environment supplies the values the authn-azure integration
|
|
6
|
+
# feature needs (see features/authn_azure.feature and features/README.md).
|
|
7
|
+
# Mirrors the layout used by conjur-api-java / conjur-api-dotnet.
|
|
8
|
+
azure:
|
|
9
|
+
RUN_AZURE_TESTS: "true"
|
|
10
|
+
AZURE_SUBSCRIPTION_ID: !var ci/azure/subscription-id
|
|
11
|
+
AZURE_RESOURCE_GROUP: !var ci/azure/authn-test/resource-group
|
|
12
|
+
USER_ASSIGNED_IDENTITY: !var ci/azure/authn-test/user-assigned-id
|
|
13
|
+
USER_ASSIGNED_IDENTITY_CLIENT_ID: !var ci/azure/authn-test/user-assigned-id-client-id
|
data/spec/authn_gcp_spec.rb
CHANGED
|
@@ -5,7 +5,6 @@ require 'cgi'
|
|
|
5
5
|
require 'conjur/api/router'
|
|
6
6
|
|
|
7
7
|
describe Conjur::API, api: :dummy do
|
|
8
|
-
let(:service_id) { 'gcp' }
|
|
9
8
|
let(:identity) { 'host/data/test/gcp-apps/test-app' }
|
|
10
9
|
let(:gcp_jwt) { 'eyJhbGciOiJSUzI1NiJ9.gcp-payload.sig' }
|
|
11
10
|
let(:raw_token) { { 'protected' => 'p', 'payload' => 'pl', 'signature' => 's' } }
|
|
@@ -24,7 +23,7 @@ describe Conjur::API, api: :dummy do
|
|
|
24
23
|
|
|
25
24
|
before do
|
|
26
25
|
allow(Conjur::API).to receive(:url_for)
|
|
27
|
-
.with(:authn_gcp_authenticate, account
|
|
26
|
+
.with(:authn_gcp_authenticate, account)
|
|
28
27
|
.and_return(resource)
|
|
29
28
|
allow(resource).to receive(:post)
|
|
30
29
|
.with("jwt=#{CGI.escape(gcp_jwt)}", content_type: 'application/x-www-form-urlencoded')
|
|
@@ -34,9 +33,7 @@ describe Conjur::API, api: :dummy do
|
|
|
34
33
|
context 'with a pre-supplied JWT' do
|
|
35
34
|
it 'returns a parsed token without calling the metadata service' do
|
|
36
35
|
expect(Conjur::API).not_to receive(:fetch_gcp_jwt)
|
|
37
|
-
token = Conjur::API.authenticate_gcp(identity, account: account,
|
|
38
|
-
service_id: service_id,
|
|
39
|
-
jwt: gcp_jwt)
|
|
36
|
+
token = Conjur::API.authenticate_gcp(identity, account: account, jwt: gcp_jwt)
|
|
40
37
|
expect(token).to eq(raw_token)
|
|
41
38
|
end
|
|
42
39
|
end
|
|
@@ -44,7 +41,7 @@ describe Conjur::API, api: :dummy do
|
|
|
44
41
|
context 'without a pre-supplied JWT' do
|
|
45
42
|
it 'fetches from the metadata service' do
|
|
46
43
|
allow(Conjur::API).to receive(:fetch_gcp_jwt).and_return(gcp_jwt)
|
|
47
|
-
token = Conjur::API.authenticate_gcp(identity, account: account
|
|
44
|
+
token = Conjur::API.authenticate_gcp(identity, account: account)
|
|
48
45
|
expect(token).to eq(raw_token)
|
|
49
46
|
end
|
|
50
47
|
end
|
|
@@ -53,27 +50,20 @@ describe Conjur::API, api: :dummy do
|
|
|
53
50
|
expect { Conjur::API.authenticate_gcp('', account: account) }
|
|
54
51
|
.to raise_error(ArgumentError, /identity is required/)
|
|
55
52
|
end
|
|
56
|
-
|
|
57
|
-
it 'raises ArgumentError when service_id is empty' do
|
|
58
|
-
expect { Conjur::API.authenticate_gcp(identity, account: account, service_id: '') }
|
|
59
|
-
.to raise_error(ArgumentError, /service_id is required/)
|
|
60
|
-
end
|
|
61
53
|
end
|
|
62
54
|
|
|
63
55
|
describe 'GCPAuthenticator#refresh_token' do
|
|
64
56
|
context 'with a pre-supplied JWT' do
|
|
65
57
|
subject(:authenticator) do
|
|
66
|
-
Conjur::API::GCPAuthenticator.new(account,
|
|
58
|
+
Conjur::API::GCPAuthenticator.new(account, identity, jwt: gcp_jwt)
|
|
67
59
|
end
|
|
68
60
|
|
|
69
61
|
it 'passes the stored JWT to authenticate_gcp' do
|
|
70
62
|
expect(Conjur::API).to receive(:authenticate_gcp)
|
|
71
63
|
.with(identity,
|
|
72
64
|
account: account,
|
|
73
|
-
service_id: service_id,
|
|
74
65
|
jwt: gcp_jwt,
|
|
75
|
-
gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL
|
|
76
|
-
audience: nil)
|
|
66
|
+
gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL)
|
|
77
67
|
.and_return(raw_token)
|
|
78
68
|
expect(authenticator.refresh_token).to eq(raw_token)
|
|
79
69
|
end
|
|
@@ -81,72 +71,29 @@ describe Conjur::API, api: :dummy do
|
|
|
81
71
|
|
|
82
72
|
context 'without a pre-supplied JWT' do
|
|
83
73
|
subject(:authenticator) do
|
|
84
|
-
Conjur::API::GCPAuthenticator.new(account,
|
|
74
|
+
Conjur::API::GCPAuthenticator.new(account, identity)
|
|
85
75
|
end
|
|
86
76
|
|
|
87
77
|
it 'passes nil jwt to authenticate_gcp (which then calls metadata service)' do
|
|
88
78
|
expect(Conjur::API).to receive(:authenticate_gcp)
|
|
89
79
|
.with(identity,
|
|
90
80
|
account: account,
|
|
91
|
-
service_id: service_id,
|
|
92
|
-
jwt: nil,
|
|
93
|
-
gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL,
|
|
94
|
-
audience: nil)
|
|
95
|
-
.and_return(raw_token)
|
|
96
|
-
expect(authenticator.refresh_token).to eq(raw_token)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
context 'with a custom audience' do
|
|
101
|
-
subject(:authenticator) do
|
|
102
|
-
Conjur::API::GCPAuthenticator.new(account, service_id, identity,
|
|
103
|
-
audience: 'my-custom-audience')
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
it 'passes the stored audience to authenticate_gcp' do
|
|
107
|
-
expect(Conjur::API).to receive(:authenticate_gcp)
|
|
108
|
-
.with(identity,
|
|
109
|
-
account: account,
|
|
110
|
-
service_id: service_id,
|
|
111
81
|
jwt: nil,
|
|
112
|
-
gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL
|
|
113
|
-
audience: 'my-custom-audience')
|
|
82
|
+
gcp_identity_url: Conjur::API::GCPAuthenticator::DEFAULT_METADATA_URL)
|
|
114
83
|
.and_return(raw_token)
|
|
115
84
|
expect(authenticator.refresh_token).to eq(raw_token)
|
|
116
85
|
end
|
|
117
86
|
end
|
|
118
87
|
end
|
|
119
88
|
|
|
120
|
-
describe '
|
|
121
|
-
it 'passes the identity and nil audience to fetch_gcp_jwt by default' do
|
|
122
|
-
allow(Conjur::API).to receive(:url_for).and_return(double(post: raw_token.to_json))
|
|
123
|
-
allow(Conjur::API).to receive(:fetch_gcp_jwt) do |acct, ident, gcp_identity_url:, audience:|
|
|
124
|
-
expect(ident).to eq(identity)
|
|
125
|
-
expect(audience).to be_nil
|
|
126
|
-
gcp_jwt
|
|
127
|
-
end
|
|
128
|
-
Conjur::API.authenticate_gcp(identity, account: account, service_id: service_id)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
it 'forwards an explicit audience to fetch_gcp_jwt' do
|
|
132
|
-
allow(Conjur::API).to receive(:url_for).and_return(double(post: raw_token.to_json))
|
|
133
|
-
allow(Conjur::API).to receive(:fetch_gcp_jwt) do |acct, ident, gcp_identity_url:, audience:|
|
|
134
|
-
expect(audience).to eq('my-custom-audience')
|
|
135
|
-
gcp_jwt
|
|
136
|
-
end
|
|
137
|
-
Conjur::API.authenticate_gcp(identity, account: account, service_id: service_id,
|
|
138
|
-
audience: 'my-custom-audience')
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
describe '.default_gcp_audience' do
|
|
89
|
+
describe '.gcp_audience' do
|
|
143
90
|
it 'strips a leading host/ and rebuilds the conjur audience' do
|
|
144
|
-
expect(Conjur::API.send(:
|
|
91
|
+
expect(Conjur::API.send(:gcp_audience, account, 'host/data/app'))
|
|
145
92
|
.to eq("conjur/#{account}/host/data/app")
|
|
146
93
|
end
|
|
147
94
|
|
|
148
95
|
it 'assumes a host identity when the host/ prefix is absent' do
|
|
149
|
-
expect(Conjur::API.send(:
|
|
96
|
+
expect(Conjur::API.send(:gcp_audience, account, 'data/app'))
|
|
150
97
|
.to eq("conjur/#{account}/host/data/app")
|
|
151
98
|
end
|
|
152
99
|
end
|
|
@@ -154,8 +101,6 @@ end
|
|
|
154
101
|
|
|
155
102
|
describe Conjur::API::Router do
|
|
156
103
|
let(:account) { 'myaccount' }
|
|
157
|
-
let(:service_id) { 'gcp' }
|
|
158
|
-
let(:identity) { 'host/data/test/gcp-apps/test-app' }
|
|
159
104
|
|
|
160
105
|
before do
|
|
161
106
|
allow(Conjur.configuration).to receive(:core_url).and_return('https://conjur.example.com')
|
|
@@ -163,14 +108,14 @@ describe Conjur::API::Router do
|
|
|
163
108
|
end
|
|
164
109
|
|
|
165
110
|
describe '#authn_gcp_authenticate' do
|
|
166
|
-
subject { described_class.authn_gcp_authenticate(account
|
|
111
|
+
subject { described_class.authn_gcp_authenticate(account) }
|
|
167
112
|
|
|
168
113
|
it 'returns a RestClient::Resource' do
|
|
169
114
|
expect(subject).to be_a(RestClient::Resource)
|
|
170
115
|
end
|
|
171
116
|
|
|
172
|
-
it 'builds the correct URL using authn-
|
|
173
|
-
expect(subject.url).to end_with('/authn-
|
|
117
|
+
it 'builds the correct URL using the serviceless authn-gcp path' do
|
|
118
|
+
expect(subject.url).to end_with('/authn-gcp/myaccount/authenticate')
|
|
174
119
|
end
|
|
175
120
|
end
|
|
176
121
|
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.pre.
|
|
4
|
+
version: 6.3.1.pre.848
|
|
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
|
|
@@ -388,6 +408,7 @@ files:
|
|
|
388
408
|
- lib/conjur/variable.rb
|
|
389
409
|
- lib/conjur/webservice.rb
|
|
390
410
|
- publish.sh
|
|
411
|
+
- secrets.yml
|
|
391
412
|
- spec/.conjurrc
|
|
392
413
|
- spec/api/host_factories_spec.rb
|
|
393
414
|
- spec/api/policies_spec.rb
|
|
@@ -443,8 +464,12 @@ rubygems_version: 4.0.10
|
|
|
443
464
|
specification_version: 4
|
|
444
465
|
summary: Conjur API
|
|
445
466
|
test_files:
|
|
467
|
+
- features/README.md
|
|
446
468
|
- features/authenticators.feature
|
|
447
469
|
- features/authn.feature
|
|
470
|
+
- features/authn_azure.feature
|
|
471
|
+
- features/authn_gcp.feature
|
|
472
|
+
- features/authn_iam.feature
|
|
448
473
|
- features/authn_local.feature
|
|
449
474
|
- features/exists.feature
|
|
450
475
|
- features/group.feature
|
|
@@ -460,6 +485,7 @@ test_files:
|
|
|
460
485
|
- features/role_fields.feature
|
|
461
486
|
- features/rotate_api_key.feature
|
|
462
487
|
- features/step_definitions/api_steps.rb
|
|
488
|
+
- features/step_definitions/cloud_authn_steps.rb
|
|
463
489
|
- features/step_definitions/policy_steps.rb
|
|
464
490
|
- features/step_definitions/result_steps.rb
|
|
465
491
|
- features/support/env.rb
|