diff --git a/CHANGELOG.md b/CHANGELOG.md index be0159611c..d64702fca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [cyberark/conjur#2901](https://github.com/cyberark/conjur/pull/2901) ### Added +- Support an optional`ca-cert` variable for providing custom certs/chains for to verify + OIDC providers or proxies when using the OIDC authenticator + [cyberark/conjur#2933](https://github.com/cyberark/conjur/pull/2933) - New flag to `conjurctl server` command called `--no-migrate` which allows for skipping the database migration step when starting the server. [cyberark/conjur#2895](https://github.com/cyberark/conjur/pull/2895) diff --git a/app/domain/authentication/authn_oidc/v2/client.rb b/app/domain/authentication/authn_oidc/v2/client.rb index b55da16cd2..660daa7e3f 100644 --- a/app/domain/authentication/authn_oidc/v2/client.rb +++ b/app/domain/authentication/authn_oidc/v2/client.rb @@ -160,8 +160,9 @@ def discovery_information(invalidate: false) force: invalidate, skip_nil: true ) do - self.discover( + self.class.discover( provider_uri: @authenticator.provider_uri, + discovery_configuration: @discovery_configuration, cert_string: @authenticator.ca_cert ) rescue Errno::ETIMEDOUT => e @@ -186,9 +187,15 @@ def self.discover( provider_uri:, discovery_configuration: ::OpenIDConnect::Discovery::Provider::Config, cert_dir: OpenSSL::X509::DEFAULT_CERT_DIR, - cert_string: nil + cert_string: nil, + jwks: false ) - d = -> { discovery_configuration.discover!(provider_uri) } + case jwks + when false + d = -> { discovery_configuration.discover!(provider_uri) } + when true + d = -> { discovery_configuration.discover!(provider_uri).jwks } + end return d.call if cert_string.blank? diff --git a/app/domain/authentication/authn_oidc/v2/strategy.rb b/app/domain/authentication/authn_oidc/v2/strategy.rb index 0b47566426..0b70d72af6 100644 --- a/app/domain/authentication/authn_oidc/v2/strategy.rb +++ b/app/domain/authentication/authn_oidc/v2/strategy.rb @@ -25,7 +25,7 @@ def callback(args) code: args[:code], nonce: args[:nonce], code_verifier: args[:code_verifier], - ca_cert: @authenticator.ca_cert + cert_string: @authenticator.ca_cert ) ) unless identity.present? diff --git a/app/domain/authentication/o_auth/discover_identity_provider.rb b/app/domain/authentication/o_auth/discover_identity_provider.rb index 0502d71c03..12ace63da3 100644 --- a/app/domain/authentication/o_auth/discover_identity_provider.rb +++ b/app/domain/authentication/o_auth/discover_identity_provider.rb @@ -30,6 +30,7 @@ def log_provider_uri def discover_provider @discovered_provider = Authentication::AuthnOidc::V2::Client.discover( provider_uri: @provider_uri, + discovery_configuration: @open_id_discovery_service, cert_string: @ca_cert ) @logger.debug( diff --git a/app/domain/authentication/o_auth/fetch_provider_keys.rb b/app/domain/authentication/o_auth/fetch_provider_keys.rb index 4c045de8e3..2395eae280 100644 --- a/app/domain/authentication/o_auth/fetch_provider_keys.rb +++ b/app/domain/authentication/o_auth/fetch_provider_keys.rb @@ -29,8 +29,14 @@ def discovered_provider end def fetch_provider_keys + # We have to invoke the custom discovery service to be able to fetch the jwks + # since it also requires the CA to invoke the jwks URI jwks = { - keys: @discovered_provider.jwks + keys: Authentication::AuthnOidc::V2::Client.discover( + provider_uri: @provider_uri, + cert_string: @ca_cert, + jwks: true + ) } algs = @discovered_provider.id_token_signing_alg_values_supported @logger.debug(LogMessages::Authentication::OAuth::FetchProviderKeysSuccess.new) diff --git a/ci/oauth/keycloak/fetch_certificate b/ci/oauth/keycloak/fetch_certificate index 9692069740..e399ff0a1c 100755 --- a/ci/oauth/keycloak/fetch_certificate +++ b/ci/oauth/keycloak/fetch_certificate @@ -13,6 +13,5 @@ openssl s_client \ -outform PEM \ >/etc/ssl/certs/keycloak.pem -# Skip this step so we can test the 'ca-cert' variable configuration -# hash=$(openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null) -# ln -s /etc/ssl/certs/keycloak.pem "/etc/ssl/certs/${hash}.0" || true +hash=$(openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null) +ln -s /etc/ssl/certs/keycloak.pem "/etc/ssl/certs/${hash}.0" || true diff --git a/ci/shared.sh b/ci/shared.sh index 081fb0b4f6..d757039d29 100644 --- a/ci/shared.sh +++ b/ci/shared.sh @@ -172,11 +172,13 @@ _run_cucumber_tests() { cucumber -ec "\ /oauth/keycloak/scripts/fetch_certificate && bundle exec parallel_cucumber . -n ${PARALLEL_PROCESSES} \ - -o '--strict --profile \"${profile}\" ${cucumber_tags_arg} \ + -o '--strict --backtrace --profile \"${profile}\" ${cucumber_tags_arg} \ --format json --out \"cucumber/$profile/cucumber_results.json\" \ --format html --out \"cucumber/$profile/cucumber_results.html\" \ --format junit --out \"cucumber/$profile/features/reports\"'" + $COMPOSE logs + # Stage 4: Coverage results # ----------------------------------------------------------- diff --git a/ci/test_suites/authenticators_oidc/test b/ci/test_suites/authenticators_oidc/test index 2f1195e1d0..3c193ecfb3 100755 --- a/ci/test_suites/authenticators_oidc/test +++ b/ci/test_suites/authenticators_oidc/test @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -e +set -ex # This file has an implicit dependency on the environment variables defined in # "ci/docker-compose.yml" keycloak section. @@ -23,6 +23,7 @@ function _hydrate_all_env_args() { # shellcheck disable=SC2034 arr=( "${keycloak_items[@]}" + "KEYCLOAK_CA_CERT=$($COMPOSE exec conjur cat /etc/ssl/certs/keycloak.pem)" "PROVIDER_URI=https://keycloak:8443/auth/realms/master" "PROVIDER_INTERNAL_URI=http://keycloak:8080/auth/realms/master/protocol/openid-connect" "PROVIDER_ISSUER=http://keycloak:8080/auth/realms/master" @@ -49,6 +50,14 @@ function main() { create_keycloak_users fetch_keycloak_certificate + # Delete the symlink so we can test with the 'ca-cert' variable + local conjur_parallel_services + read -ra conjur_parallel_services <<< "$(get_parallel_services 'conjur')" + for parallel_service in "${conjur_parallel_services[@]}"; do + hash=$($COMPOSE exec "${parallel_service}" openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null) + $COMPOSE exec "${parallel_service}" rm "/etc/ssl/certs/$hash.0" || true + done + additional_services='ldap-server keycloak' _run_cucumber_tests authenticators_oidc "$additional_services" \ _hydrate_all_env_args diff --git a/cucumber/authenticators_oidc/features/authn_oidc.feature b/cucumber/authenticators_oidc/features/authn_oidc.feature index e3a5b124e1..a2c5eb8ac6 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc.feature @@ -15,6 +15,7 @@ Feature: OIDC Authenticator - Hosts can authenticate with OIDC authenticator | oidc_client_secret | KEYCLOAK_CLIENT_SECRET | 1234 | | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_id_token_user_property | ID_TOKEN_USER_PROPERTY | preferred_username | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | And I load a policy: """ @@ -31,6 +32,9 @@ Feature: OIDC Authenticator - Hosts can authenticate with OIDC authenticator - !variable id: id-token-user-property + - !variable + id: ca-cert + - !group users - !permit @@ -48,6 +52,7 @@ Feature: OIDC Authenticator - Hosts can authenticate with OIDC authenticator | variable_id | context_variable | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_id_token_user_property | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | @smoke Scenario: A valid id token in header to get Conjur access token diff --git a/cucumber/authenticators_oidc/features/authn_oidc_bad_policy.feature b/cucumber/authenticators_oidc/features/authn_oidc_bad_policy.feature index 7713c55530..2c89226a87 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc_bad_policy.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc_bad_policy.feature @@ -15,6 +15,7 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error | oidc_client_secret | KEYCLOAK_CLIENT_SECRET | 1234 | | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_id_token_user_property | ID_TOKEN_USER_PROPERTY | preferred_username | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | @negative @acceptance Scenario: id-token-user-property variable missing in policy is denied @@ -29,6 +30,9 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error - !variable id: provider-uri + + - !variable + id: ca-cert - !group users @@ -46,6 +50,7 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error And I set the following conjur variables: | variable_id | context_variable | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | And I fetch an ID Token for username "alice" and password "alice" And I save my place in the log file When I authenticate via OIDC with id token @@ -108,6 +113,9 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error - !variable id: id-token-user-property + - !variable + id: ca-cert + - !group users - !user alice @@ -120,6 +128,7 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error | variable_id | context_variable | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_id_token_user_property | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | And I fetch an ID Token for username "alice" and password "alice" And I save my place in the log file When I authenticate via OIDC with id token @@ -146,6 +155,9 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error - !variable id: id-token-user-property + - !variable + id: ca-cert + - !group users - !permit @@ -163,6 +175,7 @@ Feature: OIDC Authenticator - Bad authenticator configuration leads to an error | variable_id | context_variable | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_id_token_user_property | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | And I fetch an ID Token for username "alice" and password "alice" And I save my place in the log file When I authenticate via OIDC with id token diff --git a/cucumber/authenticators_oidc/features/authn_oidc_performance.feature b/cucumber/authenticators_oidc/features/authn_oidc_performance.feature index fbcb8f9bcb..2b27dcdb3b 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc_performance.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc_performance.feature @@ -15,6 +15,7 @@ Feature: OIDC Authenticator - Performance tests | oidc_client_secret | KEYCLOAK_CLIENT_SECRET | 1234 | | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_id_token_user_property | ID_TOKEN_USER_PROPERTY | preferred_username | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | And I load a policy: """ @@ -31,6 +32,9 @@ Feature: OIDC Authenticator - Performance tests - !variable id: id-token-user-property + - !variable + id: ca-cert + - !group users - !permit @@ -48,6 +52,7 @@ Feature: OIDC Authenticator - Performance tests | variable_id | context_variable | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_id_token_user_property | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | @performance Scenario: successful requests diff --git a/cucumber/authenticators_oidc/features/authn_oidc_v2.feature b/cucumber/authenticators_oidc/features/authn_oidc_v2.feature index 53cce3076d..4b6daff86e 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc_v2.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc_v2.feature @@ -16,6 +16,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_claim_mapping | ID_TOKEN_USER_PROPERTY | preferred_username | | oidc_redirect_url | KEYCLOAK_REDIRECT_URI | http://conjur:3000/authn-oidc/keycloak2/cucumber/authenticate | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | And I load a policy: """ @@ -36,6 +37,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator - !variable redirect-uri - !variable provider-scope - !variable token-ttl + - !variable ca-cert - !group users - !permit role: !group users @@ -59,6 +61,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator - !variable redirect-uri - !variable provider-scope - !variable token-ttl + - !variable ca-cert - !group users - !permit role: !group users @@ -83,6 +86,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator | conjur/authn-oidc/keycloak2/claim-mapping | oidc_claim_mapping | | | conjur/authn-oidc/keycloak2/redirect-uri | oidc_redirect_url | | | conjur/authn-oidc/keycloak2/response-type | | code | + | conjur/authn-oidc/keycloak2/ca-cert | oidc_ca_cert | | | conjur/authn-oidc/keycloak2-long-lived/provider-uri | oidc_provider_uri | | | conjur/authn-oidc/keycloak2-long-lived/client-id | oidc_client_id | | | conjur/authn-oidc/keycloak2-long-lived/client-secret | oidc_client_secret | | @@ -90,6 +94,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator | conjur/authn-oidc/keycloak2-long-lived/redirect-uri | oidc_redirect_url | | | conjur/authn-oidc/keycloak2-long-lived/response-type | | code | | conjur/authn-oidc/keycloak2-long-lived/token-ttl | | PT2H | + | conjur/authn-oidc/keycloak2-long-lived/ca-cert | oidc_ca_cert | | @smoke Scenario: A valid code to get Conjur access token from webservice with default token TTL diff --git a/cucumber/authenticators_oidc/features/authn_oidc_with_ldap.feature b/cucumber/authenticators_oidc/features/authn_oidc_with_ldap.feature index 8aa2b8f06e..37d5ad53ca 100644 --- a/cucumber/authenticators_oidc/features/authn_oidc_with_ldap.feature +++ b/cucumber/authenticators_oidc/features/authn_oidc_with_ldap.feature @@ -14,6 +14,7 @@ Feature: OIDC Authenticator - Users can authenticate with OIDC & LDAP authentica | oidc_client_secret | KEYCLOAK_CLIENT_SECRET | 1234 | | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_id_token_user_property | ID_TOKEN_USER_PROPERTY | preferred_username | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | # Configure OIDC authenticator And I load a policy: @@ -28,6 +29,8 @@ Feature: OIDC Authenticator - Users can authenticate with OIDC & LDAP authentica id: provider-uri - !variable id: id-token-user-property + - !variable + id: ca-cert - !group users - !permit role: !group users @@ -42,6 +45,7 @@ Feature: OIDC Authenticator - Users can authenticate with OIDC & LDAP authentica | variable_id | context_variable | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_id_token_user_property | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | # Configure LDAP authenticator And I extend the policy with: diff --git a/cucumber/authenticators_oidc/features/authn_status_oidc.feature b/cucumber/authenticators_oidc/features/authn_status_oidc.feature index c4621e3519..9f318900bb 100644 --- a/cucumber/authenticators_oidc/features/authn_status_oidc.feature +++ b/cucumber/authenticators_oidc/features/authn_status_oidc.feature @@ -6,6 +6,7 @@ Feature: OIDC Authenticator - Status Check | context_variable | environment_variable | default_value | | oidc_provider_uri | PROVIDER_URI | https://keycloak:8443/auth/realms/master | | oidc_claim_mapping | ID_TOKEN_USER_PROPERTY | preferred_username | + | oidc_ca_cert | KEYCLOAK_CA_CERT | | @smoke Scenario: A properly configured OIDC authenticator returns a successful response @@ -28,6 +29,9 @@ Feature: OIDC Authenticator - Status Check - !variable id: id-token-user-property + + - !variable + id: ca-cert - !group users @@ -60,7 +64,7 @@ Feature: OIDC Authenticator - Status Check | variable_id | context_variable | default_value | | conjur/authn-oidc/keycloak/provider-uri | oidc_provider_uri | | | conjur/authn-oidc/keycloak/id-token-user-property | oidc_claim_mapping | | - + | conjur/authn-oidc/keycloak/ca-cert | oidc_ca_cert | | And I login as "alice" When I GET "/authn-oidc/keycloak/cucumber/status" Then the HTTP response status code is 200 diff --git a/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb b/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb index dff862dfef..ad4b8a54a3 100644 --- a/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb +++ b/cucumber/authenticators_oidc/features/step_definitions/authn_oidc_steps.rb @@ -42,6 +42,13 @@ http.use_ssl = true # Don't verify (to simplify self-signed certificate) http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + # if service_id.include?('keycloak') && ENV['KEYCLOAK_CA_CERT'] + # http.cert_store = OpenSSL::X509::Store.new + # http.cert_store.set_default_paths + # http.cert_store.add_cert(OpenSSL::X509::Certificate.new(ENV['KEYCLOAK_CA_CERT'])) + # end + request = Net::HTTP::Get.new("#{redirect_uri.path}?#{redirect_uri.query}") response = http.request(request) @@ -59,6 +66,13 @@ http = Net::HTTP.new(post_uri.host, post_uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + # if service_id.include?('keycloak') && ENV['KEYCLOAK_CA_CERT'] + # http.cert_store = OpenSSL::X509::Store.new + # http.cert_store.set_default_paths + # http.cert_store.add_cert(OpenSSL::X509::Certificate.new(ENV['KEYCLOAK_CA_CERT'])) + # end + request = Net::HTTP::Post.new(post_uri.request_uri) request['Cookie'] = cookies_arrays.join('; ') request.set_form_data({ 'username' => username, 'password' => password }) diff --git a/dev/start b/dev/start index a47cd69e12..225718d347 100755 --- a/dev/start +++ b/dev/start @@ -318,9 +318,12 @@ configure_oidc_v2() { client_add_secret "conjur/authn-oidc/$service_id/client-id" "$client_id" client_add_secret "conjur/authn-oidc/$service_id/client-secret" "$client_secret" client_add_secret "conjur/authn-oidc/$service_id/claim-mapping" "$claim_mapping" - client_add_secret "conjur/authn-oidc/$service_id/redirect_uri" "http://localhost:3000/authn-oidc/$service_id/cucumber/authenticate" + client_add_secret "conjur/authn-oidc/$service_id/redirect_uri" "http://127.0.0.1:8000/callback" if [ "$service_id" = "keycloak2" ]; then client_add_secret "conjur/authn-oidc/$service_id/ca-cert" "$($COMPOSE exec conjur cat /etc/ssl/certs/keycloak.pem)" + # Delete the symlink so we can test with the 'ca-cert' variable + hash=$($COMPOSE exec conjur openssl x509 -hash -in /etc/ssl/certs/keycloak.pem -out /dev/null) + $COMPOSE exec conjur rm "/etc/ssl/certs/$hash.0" || true fi client_load_policy "/src/conjur-server/dev/policies/authenticators/authn-oidc/$service_id-users.yml" diff --git a/spec/app/domain/authentication/o_auth/discover_identity_provider_spec.rb b/spec/app/domain/authentication/o_auth/discover_identity_provider_spec.rb index e53d7fece0..98f4b8c8ca 100644 --- a/spec/app/domain/authentication/o_auth/discover_identity_provider_spec.rb +++ b/spec/app/domain/authentication/o_auth/discover_identity_provider_spec.rb @@ -17,6 +17,18 @@ def mock_discovery_provider(error:) end end + # def mock_oidc_client(error:) + # double('oidc_client').tap do |oidc_client| + # if error + # allow (oidc_client).to receive(:discover) + # .and_raise(error) + # else + # allow (oidc_client).to receive(:discover) + # .and_return(mock_provider) + # end + # end + # end + context "A discoverable identity provider" do subject do Authentication::OAuth::DiscoverIdentityProvider.new(