Skip to content

Commit 2fccd73

Browse files
kmacinnisGitHub Enterprise
authored andcommitted
Merge pull request #1080 from Conjur-Enterprise/patch13.6.2
CNJR-10287: prepare patch 13.6.2
2 parents 66b9acc + 301d6c1 commit 2fccd73

File tree

20 files changed

+333
-109
lines changed

20 files changed

+333
-109
lines changed

.gitleaksignore

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
789e1d752e6a81676bfc7badea86f524a83e3e0d:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:27
2-
789e1d752e6a81676bfc7badea86f524a83e3e0d:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:27
3-
789e1d752e6a81676bfc7badea86f524a83e3e0d:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:37
4-
789e1d752e6a81676bfc7badea86f524a83e3e0d:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:37
1+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:33
2+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:33
3+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:34
4+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:34
5+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:35
6+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:35
7+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:36
8+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:generic-api-key:46
9+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:49
10+
7275c9fb67c307a36ef02f18bd4e7e625cd7a93f:spec/app/domain/authentication/authn_iam/authenticator_spec.rb:aws-access-token:50

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99
- Nothing should go in this section, please add to the latest unreleased version
1010
(and update the corresponding date), or add a new version.
1111

12+
## [1.22.2] - 2025-06-30
13+
### Added
14+
- Allow conjur administrator to enable additional signed headers for IAM authenticator. CNJR-10217
15+
1216
## [1.22.1] - 2025-05-02
1317
### Security
1418
- Improve headers handling in AWS IAM authenticator. CONJSE-2023
@@ -33,8 +37,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3337
- Attempt to authenticate using the built-in authenticator (`authn`) with a GET
3438
request now results in a `404` response, rather than logging an authenticator
3539
not enabled message. CNJR-5854
36-
- Attempt to load a policy that references a non-existent resource now
37-
results in a `422` response, rather than a `404` error. CNJR-9122
3840
- Set the default and maximal limit value for resources list API to 1000 in order
3941
to align with the documentation. CNJR-8485
4042
- Ensure Kubernetes authenticator websocket connections are closed when a
@@ -47,8 +49,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
4749
the secret's value. CNJR-7680
4850
- Use both database and environment configuration for the enabled authenticators.
4951
CNJR-8724
50-
51-
### Security
5252
- Update rack to 2.2.13 to address CVE-2025-27610.
5353
CONJSE-1956
5454
- Update nokogiri to 1.18.4 to address GHSA-mrxw-mxhj-p664.

Jenkinsfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ These are defined in runConjurTests, and also include the one-offs
4545
conjur_rack
4646
azure_authenticator
4747
gcp_authenticator
48+
iam_authenticator
4849
*/
4950
@Library("product-pipelines-shared-library") _
5051

@@ -1099,6 +1100,7 @@ pipeline {
10991100
authenticators_gcp/cucumber_results.html,
11001101
authenticators_status/cucumber_results.html,
11011102
authenticators_k8s/cucumber_results.html,
1103+
authenticators_iam/cucumber_results.html,
11021104
policy/cucumber_results.html,
11031105
rotators/cucumber_results.html
11041106
''',
@@ -1293,6 +1295,11 @@ def conjurTests(infrapool) {
12931295
infrapool.agentSh 'ci/test authenticators_ldap'
12941296
}
12951297
],
1298+
"authenticators_iam": [
1299+
"IAM Authenticator - ${env.STAGE_NAME}": {
1300+
infrapool.agentSh 'ci/test authenticators_iam'
1301+
}
1302+
],
12961303
"api": [
12971304
"API - ${env.STAGE_NAME}": {
12981305
infrapool.agentSh 'ci/test api'

app/controllers/secrets_controller.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def create
2828

2929
value = request.raw_post
3030

31+
validate_public_key(value) if params[:kind] == 'public_key'
32+
3133
raise ArgumentError, "'value' may not be empty" if value.blank?
3234

3335
# Only create a new secret version if the value is changed
@@ -174,6 +176,12 @@ def expire
174176

175177
private
176178

179+
def validate_public_key(value)
180+
unless value.match?(/\Assh-(rsa|ed25519|ecdsa-[a-z0-9-]+) [A-Za-z0-9+\/=]+ ?.*\z/)
181+
raise ArgumentError, "Invalid public key format"
182+
end
183+
end
184+
177185
def variable_ids
178186
return @variable_ids if @variable_ids
179187

app/domain/authentication/authn_iam/authenticator.rb

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,29 @@ module Authentication
66
module AuthnIam
77
class Authenticator
88

9-
def initialize(env:, logger: Rails.logger, client: Net::HTTP)
9+
def initialize(
10+
env:,
11+
logger: Rails.logger,
12+
client: Net::HTTP,
13+
fetch_authenticator_secrets: Authentication::Util::FetchAuthenticatorSecrets.new(
14+
optional_variable_names: %w[optional-signed-headers]
15+
))
1016
@env = env
1117
@logger = logger
1218
@client = client
19+
@fetch_authenticator_secrets = fetch_authenticator_secrets
1320
end
1421

15-
VALID_KEYS = %w[host authorization x-amz-date x-amz-security-token x-amz-content-sha256].freeze
22+
REQUIRED_KEYS = %w[
23+
host
24+
authorization
25+
x-amz-date
26+
x-amz-security-token
27+
x-amz-content-sha256].freeze
1628

1729
def valid?(input)
18-
# input.credentials is JSON holding the AWS signed headers
19-
signed_aws_headers = JSON.parse(input.credentials)
20-
.transform_keys(&:downcase)
21-
.select { |k, _| VALID_KEYS.include?(k) }
22-
raise Errors::Authentication::AuthnIam::InvalidAWSHeaders,
23-
"Headers validation failed" unless valid_headers?(signed_aws_headers)
30+
@authenticator_input = input
31+
signed_aws_headers = extract_signed_headers
2432

2533
aws_response = response_from_signed_request(signed_aws_headers)
2634

@@ -78,7 +86,7 @@ def attempt_signed_request(signed_headers)
7886
return fallback_response if fallback_response.code.to_i == 200
7987
end
8088

81-
return response
89+
response
8290
end
8391

8492
def aws_call(region:, headers:)
@@ -138,12 +146,57 @@ def valid_region?(region)
138146
/\A([a-z]{2}(-gov)?-[a-z]+-\d)\z/.match?(region)
139147
end
140148

141-
def valid_headers?(signed_aws_headers)
149+
def valid_host_header?(signed_aws_headers)
142150
host = signed_aws_headers['host']
143151
return true if host.nil? || host.empty?
144152
uri = URI("https://#{host}")
145153
uri.host&.end_with?('.amazonaws.com')
146154
end
155+
156+
def iam_authenticator_secrets
157+
@iam_authenticator_secrets ||= @fetch_authenticator_secrets.call(
158+
service_id: @authenticator_input.service_id,
159+
conjur_account: @authenticator_input.account,
160+
authenticator_name: @authenticator_input.authenticator_name,
161+
required_variable_names: [],
162+
)
163+
end
164+
165+
def optional_signed_headers
166+
@optional_signed_headers ||=
167+
(iam_authenticator_secrets&.dig('optional-signed-headers')
168+
&.to_s&.split(';')
169+
&.map(&:downcase)
170+
&.map(&:strip)) || []
171+
end
172+
173+
def extract_signed_headers
174+
input = JSON.parse(@authenticator_input.credentials).transform_keys(&:downcase)
175+
match = input['authorization']&.match(%r{SignedHeaders=([A-Za-z0-9;_-]+)})
176+
raise Errors::Authentication::AuthnIam::InvalidAWSHeaders,
177+
"Failed to extract signed headers" unless match
178+
signed_headers = match[1].split(';').map(&:downcase)
179+
180+
missing = signed_headers - input.keys
181+
raise Errors::Authentication::AuthnIam::InvalidAWSHeaders,
182+
"Missing required signed headers: #{missing.join(', ')}" unless missing.empty?
183+
184+
allowed_keys = REQUIRED_KEYS
185+
allowed_keys = allowed_keys | optional_signed_headers unless optional_signed_headers.empty?
186+
187+
unexpected = signed_headers - allowed_keys
188+
raise Errors::Authentication::AuthnIam::InvalidAWSHeaders,
189+
"Unexpected signed headers found: #{unexpected.join(', ')}. " +
190+
"Please use only permitted headers in the signature. " +
191+
"If you need to include optional headers, please ensure " +
192+
"they are secure and then add them to the authenticator " +
193+
"configuration." unless unexpected.empty?
194+
195+
raise Errors::Authentication::AuthnIam::InvalidAWSHeaders,
196+
"Host header validation failed" unless valid_host_header?(input)
197+
198+
input.select { |k, _| allowed_keys.include?(k) }
199+
end
147200
end
148201
end
149202
end

ci/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ services:
7171
RAILS_ENV: development
7272
REQUIRE_SIMPLECOV: "true"
7373
CONJUR_LOG_LEVEL: debug
74-
CONJUR_AUTHENTICATORS: authn,authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-k8s/test,authn-azure/prod,authn-gcp,authn-jwt/raw,authn-jwt/keycloak,authn-oidc/keycloak2,authn-oidc/okta-2,authn-oidc/okta,authn-oidc/keycloak2-long-lived,authn-oidc/identity
74+
CONJUR_AUTHENTICATORS: authn,authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-k8s/test,authn-azure/prod,authn-gcp,authn-jwt/raw,authn-jwt/keycloak,authn-oidc/keycloak2,authn-oidc/okta-2,authn-oidc/okta,authn-oidc/keycloak2-long-lived,authn-oidc/identity,authn-iam/prod
7575
CONJUR_FEATURE_DYNAMIC_SECRETS_ENABLED: 'true'
7676
LDAP_URI: ldap://ldap-server:389
7777
LDAP_BASE: dc=conjur,dc=net
@@ -108,7 +108,7 @@ services:
108108
RAILS_ENV: development
109109
REQUIRE_SIMPLECOV: "true"
110110
CONJUR_LOG_LEVEL: debug
111-
CONJUR_AUTHENTICATORS: authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-k8s/test,authn-azure/prod,authn-gcp,authn-jwt/raw,authn-jwt/keycloak,authn-oidc/keycloak2,authn-oidc/okta-2,authn-oidc/okta,authn-oidc/keycloak2-long-lived,authn-oidc/identity
111+
CONJUR_AUTHENTICATORS: authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-k8s/test,authn-azure/prod,authn-gcp,authn-jwt/raw,authn-jwt/keycloak,authn-oidc/keycloak2,authn-oidc/okta-2,authn-oidc/okta,authn-oidc/keycloak2-long-lived,authn-oidc/identity,authn-iam/prod
112112
CONJUR_FEATURE_DYNAMIC_SECRETS_ENABLED: 'true'
113113
LDAP_URI: ldap://ldap-server:389
114114
LDAP_BASE: dc=conjur,dc=net

ci/test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ SUBCOMMANDS
6161
authenticators_azure - Runs Cucumber Azure Authenticator features
6262
authenticators_config - Runs Cucumber Authenticator configuration features
6363
authenticators_gcp - Runs Cucumber GCP Authenticator features
64+
authenticators_iam - Runs Cucumber IAM Authenticator features
6465
authenticators_k8s - Runs Cucumber K8s Authenticator features
6566
authenticators_ldap - Runs Cucumber LDAP Authenticator features
6667
authenticators_oidc - Runs Cucumber OIDC Authenticator features
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AWS_ACCESS_KEY_ID: !var ci/aws/iam/users/dev_limited_user/access_key_id
2+
AWS_SECRET_ACCESS_KEY: !var ci/aws/iam/users/dev_limited_user/secret_access_key
3+
AWS_REGION: !var ci/aws/iam/users/dev_limited_user/region
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
# This is executed by the main "ci/test" script after cd-ing into "ci".
5+
# shellcheck disable=SC1091
6+
source "./shared.sh"
7+
8+
# The single arg is a nameref, which this function sets to an array containing
9+
# items of the form "KEY=VAL".
10+
_hydrate_iam_env_args() {
11+
# Note: Both shellcheck errors are just because arr is a nameref.
12+
# shellcheck disable=SC2178
13+
local -n arr=$1
14+
# shellcheck disable=SC2034
15+
arr=(
16+
"AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY"
17+
"AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID"
18+
"AWS_REGION=$AWS_REGION"
19+
)
20+
}
21+
22+
# Load AWS secrets from summon into the current shell
23+
eval "$(summon -f ./test_suites/authenticators_iam/secrets.yml env | grep AWS_)"
24+
25+
# Note: We pass the name of the function as the last arg, since we're
26+
# using namerefs.
27+
additional_services=''
28+
29+
_run_cucumber_tests authenticators_iam "$additional_services" _hydrate_iam_env_args

config/routes.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ def matches?(request)
8989
get "/resources/:account/:kind/*identifier" => 'resources#permitted_roles', :constraints => QueryParameterActionRecognizer.new("permitted_roles")
9090
get "/resources/:account/:kind/*identifier" => "resources#show"
9191
get "/resources/:account/:kind" => "resources#index"
92+
get "/resources/:account" => "resources#index"
93+
get "/resources" => "resources#index"
9294
end
93-
get "/resources/:account" => "resources#index"
94-
get "/resources" => "resources#index"
9595

9696
get "/:authenticator/:account/providers" => "providers#index"
9797

@@ -110,15 +110,15 @@ def matches?(request)
110110
post "/secrets/:account/:kind/*identifier" => "secrets#expire",
111111
:constraints => QueryParameterActionRecognizer.new("expirations")
112112
get "/secrets/:account/:kind/*identifier" => 'secrets#show', constraints: { kind: /variable/ }
113-
post "/secrets/:account/:kind/*identifier" => 'secrets#create', constraints: { kind: /variable|public_key|user|host|layer|group|policy|webservice/ }
113+
post "/secrets/:account/:kind/*identifier" => 'secrets#create', constraints: { kind: /variable|public_key/ }
114114
get "/secrets" => 'secrets#batch'
115115

116116
get "/policies/:account/:kind/*identifier" => 'policies#get', constraints: { kind: /policy/ }
117117
put "/policies/:account/:kind/*identifier" => 'policies#put', constraints: { kind: /policy/ }
118118
patch "/policies/:account/:kind/*identifier" => 'policies#patch', constraints: { kind: /policy/ }
119119
post "/policies/:account/:kind/*identifier" => 'policies#post', constraints: { kind: /policy/ }
120120

121-
get "/public_keys/:account/:kind/*identifier" => 'public_keys#show', constraints: { kind: /variable|public_key|user|host|layer|group|policy|webservice/ }
121+
get "/public_keys/:account/:kind/*identifier" => 'public_keys#show', constraints: { kind: /public_key|user/ }
122122

123123
post "/ca/:account/:service_id/sign" => 'certificate_authority#sign'
124124
end

0 commit comments

Comments
 (0)