conjur-api 6.3.1.pre.848 → 6.3.1

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.
data/features/README.md DELETED
@@ -1,80 +0,0 @@
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.
@@ -1,15 +0,0 @@
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"
@@ -1,19 +0,0 @@
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"
@@ -1,18 +0,0 @@
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"
@@ -1,182 +0,0 @@
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
@@ -1,145 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
- require 'cgi'
7
-
8
- module Conjur
9
- class API
10
- # Authenticator that uses an Azure Managed Identity JWT to obtain Conjur access tokens.
11
- # Fetches a JWT from the Azure Instance Metadata Service (IMDS), then POSTs it
12
- # to the Conjur authn-azure endpoint.
13
- class AzureAuthenticator
14
- include TokenExpiration
15
-
16
- IMDS_TOKEN_PATH = '/metadata/identity/oauth2/token'.freeze
17
- DEFAULT_RESOURCE = 'https://management.azure.com/'.freeze
18
- DEFAULT_IMDS_BASE = 'http://169.254.169.254'.freeze
19
- DEFAULT_IMDS_VER = '2018-02-01'.freeze
20
-
21
- attr_reader :account, :service_id, :identity, :resource_uri,
22
- :client_id, :imds_base_url, :imds_api_version
23
-
24
- def initialize(account, service_id, identity,
25
- resource_uri: DEFAULT_RESOURCE,
26
- client_id: nil,
27
- imds_base_url: DEFAULT_IMDS_BASE,
28
- imds_api_version: DEFAULT_IMDS_VER)
29
- @account = account
30
- @service_id = service_id
31
- @identity = identity
32
- @resource_uri = resource_uri
33
- @client_id = client_id
34
- @imds_base_url = imds_base_url
35
- @imds_api_version = imds_api_version
36
- update_token_born
37
- end
38
-
39
- def refresh_token
40
- Conjur::API.authenticate_azure(service_id, identity,
41
- account: account,
42
- resource_uri: resource_uri,
43
- client_id: client_id,
44
- imds_base_url: imds_base_url,
45
- imds_api_version: imds_api_version).tap do
46
- update_token_born
47
- end
48
- end
49
- end
50
-
51
- class << self
52
- # Create a {Conjur::API} instance authenticated via Azure Managed Identity (authn-azure).
53
- #
54
- # @param [String] service_id the authn-azure service ID configured in Conjur
55
- # @param [String] identity Conjur host identity (e.g. "host/my-app/azure-host")
56
- # @param [String] account the Conjur organization account
57
- # @param [String] resource_uri Azure resource URI for the IMDS token request
58
- # @param [String, nil] client_id optional client ID for a user-assigned managed identity
59
- # @param [String] imds_base_url override for the IMDS base URL (useful in tests)
60
- # @param [String] imds_api_version override for the IMDS API version
61
- # @param [String, nil] remote_ip optional IP address recorded in the audit log
62
- # @return [Conjur::API]
63
- def new_from_azure(service_id, identity,
64
- account: Conjur.configuration.account,
65
- resource_uri: AzureAuthenticator::DEFAULT_RESOURCE,
66
- client_id: nil,
67
- imds_base_url: AzureAuthenticator::DEFAULT_IMDS_BASE,
68
- imds_api_version: AzureAuthenticator::DEFAULT_IMDS_VER,
69
- remote_ip: nil)
70
- self.new.init_from_azure(service_id, identity,
71
- account: account,
72
- resource_uri: resource_uri,
73
- client_id: client_id,
74
- imds_base_url: imds_base_url,
75
- imds_api_version: imds_api_version,
76
- remote_ip: remote_ip)
77
- end
78
-
79
- # Authenticate via authn-azure and return a parsed Conjur access token.
80
- #
81
- # @param [String] service_id the authn-azure service ID
82
- # @param [String] identity Conjur host identity
83
- # @param [String] account the Conjur organization account
84
- # @return [Hash] parsed access token
85
- def authenticate_azure(service_id, identity,
86
- account: Conjur.configuration.account,
87
- resource_uri: AzureAuthenticator::DEFAULT_RESOURCE,
88
- client_id: nil,
89
- imds_base_url: AzureAuthenticator::DEFAULT_IMDS_BASE,
90
- imds_api_version: AzureAuthenticator::DEFAULT_IMDS_VER)
91
- raise ArgumentError, "service_id is required" if service_id.nil? || service_id.empty?
92
- raise ArgumentError, "identity is required" if identity.nil? || identity.empty?
93
-
94
- if Conjur.log
95
- Conjur.log << "Authenticating #{identity} to account #{account} via authn-azure/#{service_id}\n"
96
- end
97
-
98
- jwt = fetch_azure_jwt(resource_uri: resource_uri, client_id: client_id,
99
- imds_base_url: imds_base_url, imds_api_version: imds_api_version)
100
- JSON.parse(url_for(:authn_azure_authenticate, account, service_id, identity)
101
- .post("jwt=#{CGI.escape(jwt)}", content_type: 'application/x-www-form-urlencoded'))
102
- end
103
-
104
- private
105
-
106
- # Fetches an Azure Managed Identity JWT from IMDS.
107
- # Uses a direct Net::HTTP connection (proxy addr = nil) because the IMDS
108
- # link-local address 169.254.169.254 must never be routed through a proxy.
109
- def fetch_azure_jwt(resource_uri:, client_id:, imds_base_url:, imds_api_version:)
110
- query = "api-version=#{CGI.escape(imds_api_version)}&resource=#{CGI.escape(resource_uri)}"
111
- query += "&client_id=#{CGI.escape(client_id)}" if client_id
112
-
113
- uri = URI("#{imds_base_url}#{AzureAuthenticator::IMDS_TOKEN_PATH}?#{query}")
114
- http = Net::HTTP.new(uri.host, uri.port, nil) # nil = no proxy
115
- response = http.get(uri.request_uri, 'Metadata' => 'true')
116
-
117
- unless response.is_a?(Net::HTTPSuccess)
118
- raise "Azure IMDS token request failed: #{response.code} #{response.message}. Body: #{response.body}"
119
- end
120
-
121
- body = JSON.parse(response.body)
122
- token = body['access_token']
123
- raise "Azure IMDS returned an empty access token" if token.nil? || token.empty?
124
-
125
- token
126
- end
127
- end
128
-
129
- def init_from_azure(service_id, identity,
130
- account: Conjur.configuration.account,
131
- resource_uri: AzureAuthenticator::DEFAULT_RESOURCE,
132
- client_id: nil,
133
- imds_base_url: AzureAuthenticator::DEFAULT_IMDS_BASE,
134
- imds_api_version: AzureAuthenticator::DEFAULT_IMDS_VER,
135
- remote_ip: nil)
136
- @remote_ip = remote_ip
137
- @authenticator = AzureAuthenticator.new(account, service_id, identity,
138
- resource_uri: resource_uri,
139
- client_id: client_id,
140
- imds_base_url: imds_base_url,
141
- imds_api_version: imds_api_version)
142
- self
143
- end
144
- end
145
- end
@@ -1,137 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
- require 'uri'
5
- require 'cgi'
6
- require 'json'
7
-
8
- module Conjur
9
- class API
10
- # Authenticator that uses a GCP identity token to obtain Conjur access tokens.
11
- # Fetches an identity JWT from the GCP Instance Metadata Service, then POSTs it
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.
18
- #
19
- # An explicit JWT may be supplied to bypass the metadata service fetch —
20
- # useful in CI environments where the token is pre-fetched on a GCP agent.
21
- class GCPAuthenticator
22
- include TokenExpiration
23
-
24
- DEFAULT_METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1' \
25
- '/instance/service-accounts/default/identity'.freeze
26
-
27
- attr_reader :account, :identity, :jwt, :gcp_identity_url
28
-
29
- def initialize(account, identity, jwt: nil,
30
- gcp_identity_url: DEFAULT_METADATA_URL)
31
- @account = account
32
- @identity = identity
33
- @jwt = jwt
34
- @gcp_identity_url = gcp_identity_url
35
- update_token_born
36
- end
37
-
38
- def refresh_token
39
- Conjur::API.authenticate_gcp(identity, account: account,
40
- jwt: jwt,
41
- gcp_identity_url: gcp_identity_url).tap do
42
- update_token_born
43
- end
44
- end
45
- end
46
-
47
- class << self
48
- # Create a {Conjur::API} instance authenticated via GCP workload identity (authn-gcp).
49
- #
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.
53
- # @param [String] account the Conjur organization account
54
- # @param [String, nil] jwt pre-obtained GCP identity token; when supplied the metadata
55
- # service is not called. Useful in tests and CI where the token is pre-fetched.
56
- # @param [String] gcp_identity_url override for the GCP metadata identity URL (for tests)
57
- # @param [String, nil] remote_ip optional IP address recorded in the audit log
58
- # @return [Conjur::API]
59
- def new_from_gcp(identity,
60
- account: Conjur.configuration.account,
61
- jwt: nil,
62
- gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL,
63
- remote_ip: nil)
64
- self.new.init_from_gcp(identity, account: account,
65
- jwt: jwt, gcp_identity_url: gcp_identity_url,
66
- remote_ip: remote_ip)
67
- end
68
-
69
- # Authenticate via authn-gcp and return a parsed Conjur access token.
70
- #
71
- # @param [String] identity Conjur host identity (used to build the audience)
72
- # @param [String] account the Conjur organization account
73
- # @param [String, nil] jwt pre-obtained GCP identity token
74
- # @param [String] gcp_identity_url override for the GCP metadata identity URL
75
- # @return [Hash] parsed access token
76
- def authenticate_gcp(identity,
77
- account: Conjur.configuration.account,
78
- jwt: nil,
79
- gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL)
80
- raise ArgumentError, "identity is required" if identity.nil? || identity.empty?
81
-
82
- if Conjur.log
83
- Conjur.log << "Authenticating #{identity} to account #{account} via authn-gcp\n"
84
- end
85
-
86
- token = jwt || fetch_gcp_jwt(account, identity, gcp_identity_url: gcp_identity_url)
87
- JSON.parse(url_for(:authn_gcp_authenticate, account)
88
- .post("jwt=#{CGI.escape(token)}", content_type: 'application/x-www-form-urlencoded'))
89
- end
90
-
91
- private
92
-
93
- # Fetches a GCP identity token from the instance metadata service.
94
- # Uses a direct Net::HTTP connection (proxy addr = nil) because the metadata
95
- # link-local address must never be routed through a proxy.
96
- #
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)
101
- url = "#{gcp_identity_url}?#{URI.encode_www_form(audience: aud, format: 'full')}"
102
-
103
- uri = URI(url)
104
- http = Net::HTTP.new(uri.host, uri.port, nil) # nil = no proxy
105
- response = http.get(uri.request_uri, 'Metadata-Flavor' => 'Google')
106
-
107
- unless response.is_a?(Net::HTTPSuccess)
108
- raise "GCP metadata token request failed: #{response.code} #{response.message}. Body: #{response.body}"
109
- end
110
-
111
- token = response.body&.strip
112
- raise "GCP metadata service returned an empty token" if token.nil? || token.empty?
113
-
114
- token
115
- end
116
-
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
119
- # supplied identity and is normalized away before being re-applied.
120
- def gcp_audience(account, identity)
121
- normalized = identity.start_with?('host/') ? identity['host/'.length..] : identity
122
- "conjur/#{account}/host/#{normalized}"
123
- end
124
- end
125
-
126
- def init_from_gcp(identity,
127
- account: Conjur.configuration.account,
128
- jwt: nil,
129
- gcp_identity_url: GCPAuthenticator::DEFAULT_METADATA_URL,
130
- remote_ip: nil)
131
- @remote_ip = remote_ip
132
- @authenticator = GCPAuthenticator.new(account, identity,
133
- jwt: jwt, gcp_identity_url: gcp_identity_url)
134
- self
135
- end
136
- end
137
- end