diff --git a/CHANGELOG.md b/CHANGELOG.md index e152fa14b0..7ee58ded12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +- Enabled authenticators can now be configured via a configuration file, or the + CONJUR_AUTHENTICATORS environment variable. + [cyberark/conjur##2173](https://github.com/cyberark/conjur/issues/#2173) - Trusted Proxies can now be configured with a configuration file or by setting the CONJUR_TRUSTED_PROXIES environment variable. [cyberark/conjur#2168](https://github.com/cyberark/conjur/issues/2168) diff --git a/Gemfile b/Gemfile index d8f3ae789e..9e4b1f95fc 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,10 @@ gem 'websocket' gem 'jwt' gem 'openid_connect' -gem "anyway_config", "~> 2.0" +# Unpin version once this Github issue, +# https://github.com/palkan/anyway_config/issues/82 +# is resolved +gem "anyway_config", "2.1.0" group :development, :test do gem 'aruba' diff --git a/Gemfile.lock b/Gemfile.lock index 493dcd454e..171e656627 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -444,7 +444,7 @@ PLATFORMS DEPENDENCIES activesupport - anyway_config (~> 2.0) + anyway_config (= 2.1.0) aruba aws-sdk-iam base32-crockford diff --git a/app/controllers/authenticate_controller.rb b/app/controllers/authenticate_controller.rb index 4809abc3ec..fd680b4f47 100644 --- a/app/controllers/authenticate_controller.rb +++ b/app/controllers/authenticate_controller.rb @@ -23,7 +23,7 @@ def index def status Authentication::ValidateStatus.new.( authenticator_status_input: status_input, - enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str(ENV) + enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str ) render(json: { status: "ok" }) rescue => e @@ -70,7 +70,7 @@ def authenticate(input = authenticator_input) authn_token = Authentication::Authenticate.new.( authenticator_input: input, authenticators: installed_authenticators, - enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str(ENV) + enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str ) content_type = :json if encoded_response? @@ -237,7 +237,7 @@ def installed_authenticators end def enabled_authenticators - Authentication::InstalledAuthenticators.enabled_authenticators(ENV) + Authentication::InstalledAuthenticators.enabled_authenticators end def encoded_response? diff --git a/app/controllers/concerns/basic_authenticator.rb b/app/controllers/concerns/basic_authenticator.rb index 6584316cb2..1506bce169 100644 --- a/app/controllers/concerns/basic_authenticator.rb +++ b/app/controllers/concerns/basic_authenticator.rb @@ -30,7 +30,7 @@ def authenticator_login(username, password) ::Authentication::Login.new.( authenticator_input: login_input(username, password), authenticators: installed_login_authenticators, - enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str(ENV) + enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str ) end diff --git a/app/domain/authentication/authn_k8s/validate_pod_request.rb b/app/domain/authentication/authn_k8s/validate_pod_request.rb index b6f70a03f7..f764cfdcc9 100644 --- a/app/domain/authentication/authn_k8s/validate_pod_request.rb +++ b/app/domain/authentication/authn_k8s/validate_pod_request.rb @@ -12,7 +12,7 @@ module AuthnK8s k8s_object_lookup_class: K8sObjectLookup, validate_webservice_is_whitelisted: Security::ValidateWebserviceIsWhitelisted.new, validate_role_can_access_webservice: Security::ValidateRoleCanAccessWebservice.new, - enabled_authenticators: InstalledAuthenticators.enabled_authenticators_str(ENV), + enabled_authenticators: InstalledAuthenticators.enabled_authenticators_str, validate_resource_restrictions: ResourceRestrictions::ValidateResourceRestrictions.new( extract_resource_restrictions: ExtractK8sResourceRestrictions.new ), diff --git a/app/domain/authentication/installed_authenticators.rb b/app/domain/authentication/installed_authenticators.rb index 809aaf73aa..295d5c19de 100644 --- a/app/domain/authentication/installed_authenticators.rb +++ b/app/domain/authentication/installed_authenticators.rb @@ -33,21 +33,18 @@ def configured_authenticators .push(::Authentication::Common.default_authenticator_name) end - def enabled_authenticators(env) + def enabled_authenticators # Enabling via environment overrides enabling via CLI - env_enabled_authenticators(env) || db_enabled_authenticators + authenticators = + Rails.application.config.conjur_config.authenticators + authenticators.empty? ? db_enabled_authenticators : authenticators end - def enabled_authenticators_str(env) - enabled_authenticators(env).join(',') + def enabled_authenticators_str + enabled_authenticators.join(',') end private - - def env_enabled_authenticators(env) - authenticators = env["CONJUR_AUTHENTICATORS"] - authenticators.present? ? authenticators.split(',') : nil - end def db_enabled_authenticators # Always include 'authn' when enabling authenticators via CLI so that it diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 4c9588cd7b..8a8efd6451 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -44,7 +44,7 @@ services: RAILS_ENV: REQUIRE_SIMPLECOV: "true" CONJUR_LOG_LEVEL: debug - CONJUR_AUTHENTICATORS: authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-config/env,authn-azure/prod,authn-gcp + CONJUR_AUTHENTICATORS: authn-ldap/test,authn-ldap/secure,authn-oidc/keycloak,authn-oidc,authn-k8s/test,authn-azure/prod,authn-gcp LDAP_URI: ldap://ldap-server:389 LDAP_BASE: dc=conjur,dc=net LDAP_FILTER: '(uid=%s)' diff --git a/cucumber/authenticators_config/features/authn_config.feature b/cucumber/authenticators_config/features/authn_config.feature index 9b6795afb5..27a3702bb1 100644 --- a/cucumber/authenticators_config/features/authn_config.feature +++ b/cucumber/authenticators_config/features/authn_config.feature @@ -5,15 +5,15 @@ Feature: Authenticator configuration And I have user "authn-updater" And I load a policy: """ - # authn-config/env is enabled in CONJUR_AUTHENTICATORS and used in tests to + # authn-k8s/test is enabled in CONJUR_AUTHENTICATORS and used in tests to # ensure that the env value continues to be used even when DB config exists. - !policy - id: conjur/authn-config/env + id: conjur/authn-k8s/test body: - !webservice - !policy - id: conjur/authn-config/db + id: conjur/authn-k8s/db body: - !webservice @@ -35,48 +35,48 @@ Feature: Authenticator configuration resource: !webservice - !grant - role: !group conjur/authn-config/db/viewers + role: !group conjur/authn-k8s/db/viewers member: !user authn-viewer - !grant - role: !group conjur/authn-config/db/updaters + role: !group conjur/authn-k8s/db/updaters member: !user authn-updater """ Scenario: Authenticator is not configured in database When I am the super-user And I retrieve the list of authenticators - Then the enabled authenticators contains "authn-config/env" + Then the enabled authenticators contains "authn-k8s/test" Scenario: Authenticator is enabled in the environment and disabled in the database When I am the super-user - And I successfully PATCH "/authn-config/env/cucumber" with body: + And I successfully PATCH "/authn-k8s/test/cucumber" with body: """ enabled=false """ And I retrieve the list of authenticators - Then the enabled authenticators contains "authn-config/env" + Then the enabled authenticators contains "authn-k8s/test" Scenario: Authenticator is successfully configured When I login as "authn-updater" - And I successfully PATCH "/authn-config/db/cucumber" with body: + And I successfully PATCH "/authn-k8s/db/cucumber" with body: """ enabled=true """ Then the HTTP response status code is 204 - And authenticator "cucumber:webservice:conjur/authn-config/db" is enabled + And authenticator "cucumber:webservice:conjur/authn-k8s/db" is enabled - When I successfully PATCH "/authn-config/db/cucumber" with body: + When I successfully PATCH "/authn-k8s/db/cucumber" with body: """ enabled=false """ Then the HTTP response status code is 204 - And authenticator "cucumber:webservice:conjur/authn-config/db" is disabled + And authenticator "cucumber:webservice:conjur/authn-k8s/db" is disabled Scenario: Authenticator account does not exist When I am the super-user And I save my place in the log file - And I PATCH "/authn-config/db/nope" with body: + And I PATCH "/authn-k8s/db/nope" with body: """ enabled=true """ @@ -89,7 +89,7 @@ Feature: Authenticator configuration Scenario: Authenticator webservice does not exist When I am the super-user And I save my place in the log file - And I PATCH "/authn-config/db%2Fnope/cucumber" with body: + And I PATCH "/authn-k8s/db%2Fnope/cucumber" with body: """ enabled=true """ @@ -102,12 +102,12 @@ Feature: Authenticator configuration Scenario: Authenticated user can not update authenticator When I login as "authn-viewer" And I save my place in the log file - And I PATCH "/authn-config/db/cucumber" with body: + And I PATCH "/authn-k8s/db/cucumber" with body: """ enabled=true """ Then the HTTP response status code is 403 - And authenticator "cucumber:webservice:conjur/authn-config/db" is disabled + And authenticator "cucumber:webservice:conjur/authn-k8s/db" is disabled And The following appears in the log after my savepoint: """ Errors::Authentication::Security::RoleNotAuthorizedOnResource @@ -116,7 +116,7 @@ Feature: Authenticator configuration Scenario: Nested webservice can not be configured When I am the super-user And I save my place in the log file - And I PATCH "/authn-config/db%2Fstatus/cucumber" with body: + And I PATCH "/authn-k8s/db%2Fstatus/cucumber" with body: """ enabled=true """ diff --git a/dev/start b/dev/start index c69eadc354..626cd7f2b8 100755 --- a/dev/start +++ b/dev/start @@ -136,7 +136,7 @@ docker-compose exec -d conjur conjurctl server echo 'Checking if Conjur server is ready' conjur_isready? -default_authenticators="authn,authn-config/env" +default_authenticators="authn,authn-k8s/test" enabled_authenticators="$default_authenticators" env_args= diff --git a/lib/conjur/conjur_config.rb b/lib/conjur/conjur_config.rb index 1e6f0c3d61..e256436b2c 100644 --- a/lib/conjur/conjur_config.rb +++ b/lib/conjur/conjur_config.rb @@ -16,7 +16,8 @@ class ConjurConfig < Anyway::Config attr_config( # Read TRUSTED_PROXIES before default to maintain backwards compatibility - trusted_proxies: (ENV['TRUSTED_PROXIES'] || []) + trusted_proxies: (ENV['TRUSTED_PROXIES'] || []), + authenticators: [] ) # Perform validations and collect errors then report results as exception @@ -24,6 +25,7 @@ class ConjurConfig < Anyway::Config invalid = [] invalid << "trusted_proxies" unless trusted_proxies_valid? + invalid << "authenticators" unless authenticators_valid? unless invalid.empty? raise Errors::Conjur::InvalidConfigValues, invalid.join(', ') @@ -32,7 +34,7 @@ class ConjurConfig < Anyway::Config # Get attribute sources without including attribute values def attribute_sources - to_source_trace.map { |k,v| [ k.to_sym, v[:source][:type] ] }.to_h + to_source_trace.map { |k, v| [ k.to_sym, v[:source][:type] ] }.to_h end # The Anyway config gem automatically converts a comma-separated env var to @@ -43,9 +45,15 @@ def attribute_sources # # To address this inconsistent behavior, we use custom setters to ensure # list attributes are always converted to a a list type. + # We filed an issue regarding this behavior: + # https://github.com/palkan/anyway_config/issues/82 def trusted_proxies=(val) - super(str_to_list(val)) + super(str_to_list(val)&.uniq) + end + + def authenticators=(val) + super(str_to_list(val)&.uniq) end private @@ -63,5 +71,18 @@ def trusted_proxies_valid? rescue false end + + def authenticators_valid? + # TODO: Ideally we would check against the enabled authenticators + # in the DB. However, we need to figure out how to use code from the + # application without introducing warnings. + authenticators_regex = + %r{^(authn|authn-(k8s|oidc|iam|ldap|gcp|azure)(/.+)?)$} + authenticators.all? do |authenticator| + authenticators_regex.match?(authenticator.strip) + end + rescue + false + end end end diff --git a/spec/lib/conjur/conjur_config_spec.rb b/spec/lib/conjur/conjur_config_spec.rb index a0a86a58b6..d535d7ffbb 100644 --- a/spec/lib/conjur/conjur_config_spec.rb +++ b/spec/lib/conjur/conjur_config_spec.rb @@ -86,23 +86,45 @@ end describe "validation" do - let(:invalid_config) { + let(:invalid_trusted_proxies_config) { Conjur::ConjurConfig.new(trusted_proxies: "boop") } + let(:invalid_authenticators_config) { + Conjur::ConjurConfig.new(authenticators: "invalid-authn") + } + + let(:invalid_config) { + Conjur::ConjurConfig.new( + authenticators: "invalid-authn", trusted_proxies: "boop" + ) + } + it "raises error when validation fails" do + expect { invalid_trusted_proxies_config }. + to raise_error(Errors::Conjur::InvalidConfigValues) + expect { invalid_authenticators_config }. + to raise_error(Errors::Conjur::InvalidConfigValues) expect { invalid_config }. to raise_error(Errors::Conjur::InvalidConfigValues) end it "includes the attribute that failed validation" do - expect { invalid_config }. + expect { invalid_trusted_proxies_config }. to raise_error(/trusted_proxies/) + expect { invalid_authenticators_config }. + to raise_error(/authenticators/) + expect { invalid_config }. + to raise_error(/trusted_proxies, authenticators/) end it "does not include the value that failed validation" do - expect { invalid_config }. + expect { invalid_trusted_proxies_config }. to_not raise_error(/boop/) + expect { invalid_authenticators_config }. + to_not raise_error(/invalid-authn/) + expect { invalid_config }. + to_not raise_error(/boop.*invalid-authn/) end end