Skip to content

Commit 40401f4

Browse files
Merge pull request #2988 from cyberark/cert-writing-bug
AuthnOIDC V2: write custom certs to non-default directory
2 parents f5dabb6 + 1d079cd commit 40401f4

File tree

6 files changed

+149
-12
lines changed

6 files changed

+149
-12
lines changed

CHANGELOG.md

+16-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ 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.20.1] - 2023-10-13
13+
14+
### Fixed
15+
- OIDC Authenticator now writes custom certs to a non-default directory instead
16+
of the system default certificate store.
17+
[cyberark/conjur#2988](https://github.com/cyberark/conjur/pull/2988)
18+
19+
### Added
20+
- Support for the no_proxy & NO_PROXY environment variables for the k8s authenticator.
21+
[CNJR-2759](https://ca-il-jira.il.cyber-ark.com:8443/browse/CNJR-2759)
22+
23+
### Security
24+
- Upgrade google/cloud-sdk in ci/test_suites/authenticators_k8s/dev/Dockerfile/test
25+
to use latest version (448.0.0)
26+
[cyberark/conjur#2972](https://github.com/cyberark/conjur/pull/2972)
27+
1228
## [1.20.0] - 2023-09-21
1329

1430
### Fixed
@@ -34,8 +50,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3450
- Use base images with newer Ubuntu and UBI.
3551
Display FIPS Mode status in the UI (requires temporary fix for OpenSSL gem).
3652
[cyberark/conjur#2874](https://github.com/cyberark/conjur/pull/2874)
37-
- Support for the no_proxy & NO_PROXY environment variables for the k8s authenticator.
38-
[CNJR-2759](https://ca-il-jira.il.cyber-ark.com:8443/browse/CNJR-2759)
3953

4054
### Changed
4155
- The database thread pool max connection size is now based on the number of
@@ -51,9 +65,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5165
[cyberark/conjur#2827](https://github.com/cyberark/conjur/pull/2827)
5266

5367
### Security
54-
- Upgrade google/cloud-sdk in ci/test_suites/authenticators_k8s/dev/Dockerfile/test
55-
to use latest version (448.0.0)
56-
[cyberark/conjur#2972](https://github.com/cyberark/conjur/pull/2972)
5768
- Support plural syntax for revoke and deny
5869
[cyberark/conjur#2901](https://github.com/cyberark/conjur/pull/2901)
5970
- Previously, attempting to add and remove a privilege in the same policy load

app/domain/authentication/authn_oidc/v2/client.rb

+59-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ def initialize(
1818
@logger = logger
1919
end
2020

21+
# Writing certificates to the default system cert store requires
22+
# superuser privilege. Instead, Conjur will use ${CONJUR_ROOT}/tmp/certs.
23+
def self.default_cert_dir(dir: Dir, fileutils: FileUtils)
24+
if @default_cert_dir.blank?
25+
conjur_root = __dir__.slice(0..(__dir__.index('/app')))
26+
@default_cert_dir = File.join(conjur_root, "tmp/certs")
27+
end
28+
29+
fileutils.mkdir_p(@default_cert_dir) unless dir.exist?(@default_cert_dir.to_s)
30+
31+
@default_cert_dir
32+
end
33+
2134
def oidc_client
2235
@oidc_client ||= begin
2336
issuer_uri = URI(@authenticator.provider_uri)
@@ -99,7 +112,7 @@ def callback(code:, nonce:, code_verifier: nil)
99112

100113
# callback_with_temporary_cert wraps the callback method with commands
101114
# to write & clean up a given certificate or cert chain in a given
102-
# directory. By default, Conjur's default cert store is used.
115+
# directory. By default, ${CONJUR_ROOT}/tmp/certs is used.
103116
#
104117
# The temporary certificate file name is "x.n", where x is the hash of
105118
# the certificate subject name, and n is incrememnted from 0 in case of
@@ -114,7 +127,7 @@ def callback_with_temporary_cert(
114127
code:,
115128
nonce:,
116129
code_verifier: nil,
117-
cert_dir: OpenSSL::X509::DEFAULT_CERT_DIR,
130+
cert_dir: Authentication::AuthnOidc::V2::Client.default_cert_dir,
118131
cert_string: nil
119132
)
120133
c = -> { callback(code: code, nonce: nonce, code_verifier: code_verifier) }
@@ -148,6 +161,27 @@ def callback_with_temporary_cert(
148161
symlink_a << symlink
149162
end
150163

164+
if OpenIDConnect.http_config.nil? || OpenIDConnect.http_client.ssl.ca_path != cert_dir
165+
config_proc = proc do |config|
166+
config.ssl.ca_path = cert_dir
167+
config.ssl.verify_mode = OpenSSL::SSL::VERIFY_PEER
168+
end
169+
170+
# OpenIDConnect gem only accepts a single Faraday configuration
171+
# through calls to its .http_config method, and future calls to
172+
# the #http_config method return the first config instance.
173+
#
174+
# On the first call to OpenIDConnect.http_config, it will pass the
175+
# new Faraday configuration to its dependency gems that also have
176+
# nil configs. We can't be certain that each gem is configured
177+
# with the same Faraday config and need them synchronized, so we
178+
# inject them manually.
179+
OpenIDConnect.class_variable_set(:@@http_config, config_proc)
180+
WebFinger.instance_variable_set(:@http_config, config_proc)
181+
SWD.class_variable_set(:@@http_config, config_proc)
182+
Rack::OAuth2.class_variable_set(:@@http_config, config_proc)
183+
end
184+
151185
c.call
152186
ensure
153187
symlink_a.each{ |s| File.unlink(s) if s.present? && File.symlink?(s) }
@@ -174,7 +208,7 @@ def discovery_information(invalidate: false)
174208

175209
# discover wraps ::OpenIDConnect::Discovery::Provider::Config.discover!
176210
# with commands to write & clean up a given certificate or cert chain in
177-
# a given directory. By default, Conjur's default cert store is used.
211+
# a given directory. By default, ${CONJUR_ROOT}/tmp/certs is used.
178212
#
179213
# The temporary certificate file name is "x.n", where x is the hash of
180214
# the certificate subject name, and n is incremented from 0 in case of
@@ -186,7 +220,7 @@ def discovery_information(invalidate: false)
186220
def self.discover(
187221
provider_uri:,
188222
discovery_configuration: ::OpenIDConnect::Discovery::Provider::Config,
189-
cert_dir: OpenSSL::X509::DEFAULT_CERT_DIR,
223+
cert_dir: default_cert_dir,
190224
cert_string: nil,
191225
jwks: false
192226
)
@@ -226,6 +260,27 @@ def self.discover(
226260
symlink_a << symlink
227261
end
228262

263+
if OpenIDConnect.http_config.nil? || OpenIDConnect.http_client.ssl.ca_path != cert_dir
264+
config_proc = proc do |config|
265+
config.ssl.ca_path = cert_dir
266+
config.ssl.verify_mode = OpenSSL::SSL::VERIFY_PEER
267+
end
268+
269+
# OpenIDConnect gem only accepts a single Faraday configuration
270+
# through calls to its .http_config method, and future calls to
271+
# the #http_config method return the first config instance.
272+
#
273+
# On the first call to OpenIDConnect.http_config, it will pass the
274+
# new Faraday configuration to its dependency gems that also have
275+
# nil configs. We can't be certain that each gem is configured
276+
# with the same Faraday config and need them synchronized, so we
277+
# inject them manually.
278+
OpenIDConnect.class_variable_set(:@@http_config, config_proc)
279+
WebFinger.instance_variable_set(:@http_config, config_proc)
280+
SWD.class_variable_set(:@@http_config, config_proc)
281+
Rack::OAuth2.class_variable_set(:@@http_config, config_proc)
282+
end
283+
229284
d.call
230285
ensure
231286
symlink_a.each{ |s| File.unlink(s) if s.present? && File.symlink?(s) }

ci/shared.sh

-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ _run_cucumber_tests() {
170170
# ${cucumber_tags_arg} should overwrite the profile tags in a way for @smoke to work correctly
171171
$COMPOSE run "${run_flags[@]}" "${env_var_flags[@]}" \
172172
cucumber -ec "\
173-
/oauth/keycloak/scripts/fetch_certificate &&
174173
bundle exec parallel_cucumber . -n ${PARALLEL_PROCESSES} \
175174
-o '--strict --profile \"${profile}\" ${cucumber_tags_arg} \
176175
--format json --out \"cucumber/$profile/cucumber_results.json\" \

ci/test_suites/authenticators_oidc/test

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function main() {
5454
local conjur_parallel_services
5555
read -ra conjur_parallel_services <<< "$(get_parallel_services 'conjur')"
5656
for parallel_service in "${conjur_parallel_services[@]}"; do
57-
hash=$($COMPOSE exec "${parallel_service}" openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null)
57+
hash=$($COMPOSE exec "${parallel_service}" openssl x509 -hash -in /etc/ssl/certs/keycloak.pem --noout)
5858
$COMPOSE exec "${parallel_service}" rm "/etc/ssl/certs/$hash.0" || true
5959
done
6060

dev/start

+1-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ configure_oidc_v2() {
322322
if [ "$service_id" = "keycloak2" ]; then
323323
client_add_secret "conjur/authn-oidc/$service_id/ca-cert" "$($COMPOSE exec conjur cat /etc/ssl/certs/keycloak.pem)"
324324
# Delete the symlink so we can test with the 'ca-cert' variable
325-
hash=$($COMPOSE exec conjur openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null)
325+
hash=$($COMPOSE exec conjur openssl x509 -hash -in /etc/ssl/certs/keycloak.pem --noout)
326326
$COMPOSE exec conjur rm "/etc/ssl/certs/$hash.0" || true
327327
fi
328328

spec/app/domain/authentication/authn-oidc/v2/client_spec.rb

+72
Original file line numberDiff line numberDiff line change
@@ -644,4 +644,76 @@ def client(config)
644644
end
645645
end
646646
end
647+
648+
describe '.default_cert_dir', type: 'unit' do
649+
let(:target) { Authentication::AuthnOidc::V2::Client }
650+
let(:conjur_root) { "/src/conjur-server" }
651+
let(:dir) { double("Mock Dir") }
652+
let(:fileutils) { double("Mock FileUtils") }
653+
654+
context 'when @default_cert_dir is blank' do
655+
before(:each) do
656+
target.instance_variable_set(:@default_cert_dir, nil)
657+
end
658+
659+
context 'when default cert dir exists in filesystem' do
660+
before(:each) do
661+
allow(dir).to receive(:exist?).with(String).and_return(true)
662+
end
663+
664+
it 'returns the default path' do
665+
expect(target.default_cert_dir(dir: dir, fileutils: fileutils)).to eq("#{conjur_root}/tmp/certs")
666+
expect(target.instance_variable_get(:@default_cert_dir)).to eq("#{conjur_root}/tmp/certs")
667+
end
668+
end
669+
670+
context 'when the default cert dir does not exist in filesystem' do
671+
before(:each) do
672+
allow(dir).to receive(:exist?).with(String).and_return(false)
673+
allow(fileutils).to receive(:mkdir_p).with(String) do |s|
674+
[s]
675+
end
676+
end
677+
678+
it 'creates the dir and returns the default path' do
679+
expect(fileutils).to receive(:mkdir_p).with(String)
680+
expect(target.default_cert_dir(dir: dir, fileutils: fileutils)).to eq("#{conjur_root}/tmp/certs")
681+
expect(target.instance_variable_get(:@default_cert_dir)).to eq("#{conjur_root}/tmp/certs")
682+
end
683+
end
684+
685+
end
686+
687+
context 'when @default_cert_dir is not blank' do
688+
before(:each) do
689+
target.instance_variable_set(:@default_cert_dir, "/path/to/dir")
690+
end
691+
692+
context 'when @default_cert_dir exists in filesystem' do
693+
before(:each) do
694+
allow(dir).to receive(:exist?).with(String).and_return(true)
695+
end
696+
697+
it 'returns existing path' do
698+
expect(target.default_cert_dir(dir: dir, fileutils: fileutils)).to eq("/path/to/dir")
699+
expect(target.instance_variable_get(:@default_cert_dir)).to eq("/path/to/dir")
700+
end
701+
end
702+
703+
context 'when @default_cert_dir does not exist in filesystem' do
704+
before(:each) do
705+
allow(dir).to receive(:exist?).with(String).and_return(false)
706+
allow(fileutils).to receive(:mkdir_p).with(String) do |s|
707+
[s]
708+
end
709+
end
710+
711+
it 'creates the dir and returns the existing path' do
712+
expect(fileutils).to receive(:mkdir_p).with(String)
713+
expect(target.default_cert_dir(dir: dir, fileutils: fileutils)).to eq("/path/to/dir")
714+
expect(target.instance_variable_get(:@default_cert_dir)).to eq("/path/to/dir")
715+
end
716+
end
717+
end
718+
end
647719
end

0 commit comments

Comments
 (0)