Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ gem 'rack-rewrite'

gem 'dry-struct'
gem 'dry-types'
gem 'dry-validation'
gem 'net-ldap'

# for AWS rotator
Expand Down
15 changes: 15 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,17 @@ GEM
dry-core (0.7.1)
concurrent-ruby (~> 1.0)
dry-inflector (0.2.1)
dry-initializer (3.1.1)
dry-logic (1.2.0)
concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5)
dry-schema (1.10.6)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.13, >= 0.13.0)
dry-core (~> 0.5, >= 0.5)
dry-initializer (~> 3.0)
dry-logic (~> 1.2)
dry-types (~> 1.5)
dry-struct (1.4.0)
dry-core (~> 0.5, >= 0.5)
dry-types (~> 1.5)
Expand All @@ -207,6 +215,12 @@ GEM
dry-core (~> 0.5, >= 0.5)
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
dry-validation (1.8.1)
concurrent-ruby (~> 1.0)
dry-container (~> 0.7, >= 0.7.1)
dry-core (~> 0.5, >= 0.5)
dry-initializer (~> 3.0)
dry-schema (~> 1.8, >= 1.8.0)
erubi (1.12.0)
event_emitter (0.2.6)
eventmachine (1.2.7)
Expand Down Expand Up @@ -517,6 +531,7 @@ DEPENDENCIES
debase (~> 0.2.5.beta2)
dry-struct
dry-types
dry-validation
event_emitter
faye-websocket
ffi (>= 1.9.24)
Expand Down
66 changes: 34 additions & 32 deletions app/controllers/authenticate_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,31 @@ class AuthenticateController < ApplicationController
include BasicAuthenticator
include AuthorizeResource

def oidc_authenticate_code_redirect
def authenticate_via_get
handler = Authentication::Handler::AuthenticationHandler.new(
authenticator_type: params[:authenticator]
)
# TODO: need a mechanism for an authenticator strategy to define the required
# params. This will likely need to be done via the Handler.
params.permit!
params.permit(handler.params_allowed)

auth_token = handler.call(
parameters: params.to_hash.symbolize_keys,
request_ip: request.ip
)

render_authn_token(auth_token)
rescue => e
log_backtrace(e)
raise e
end

def authenticate_via_post
auth_token = Authentication::Handler::AuthenticationHandler.new(
authenticator_type: params[:authenticator]
).call(
parameters: params.to_hash.symbolize_keys,
parameters: params,
request_body: request.body.read,
request_ip: request.ip
)

Expand All @@ -22,6 +38,21 @@ def oidc_authenticate_code_redirect
raise e
end

def authenticator_status
# binding.pry
Authentication::Handler::StatusHandler.new(
authenticator_type: params[:authenticator]
).call(
parameters: params.permit(:account, :service_id, :authenticator).to_hash.symbolize_keys,
role: current_user,
request_ip: request.ip
)
render(json: { status: "ok" })
rescue => e
log_backtrace(e)
render(status_failure_response(e))
end

def index
authenticators = {
# Installed authenticator plugins
Expand Down Expand Up @@ -68,18 +99,6 @@ def status_input
)
end

def authn_jwt_status
params[:authenticator] = "authn-jwt"
Authentication::AuthnJwt::ValidateStatus.new.call(
authenticator_status_input: status_input,
enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str
)
render(json: { status: "ok" })
rescue => e
log_backtrace(e)
render(status_failure_response(e))
end

def update_config
Authentication::UpdateAuthenticatorConfig.new.(
update_config_input: update_config_input
Expand Down Expand Up @@ -118,23 +137,6 @@ def login
handle_login_error(e)
end

def authenticate_jwt
params[:authenticator] = "authn-jwt"
authn_token = Authentication::AuthnJwt::OrchestrateAuthentication.new.call(
authenticator_input: authenticator_input_without_credentials,
enabled_authenticators: Authentication::InstalledAuthenticators.enabled_authenticators_str
)
render_authn_token(authn_token)
rescue => e
# At this point authenticator_input.username is always empty (e.g. cucumber:user:USERNAME_MISSING)
log_audit_failure(
authn_params: authenticator_input,
audit_event_class: Audit::Event::Authn::Authenticate,
error: e
)
handle_authentication_error(e)
end

# Update the input to have the username from the token and authenticate
def authenticate_oidc
params[:authenticator] = "authn-oidc"
Expand Down
70 changes: 44 additions & 26 deletions app/db/repository/authenticator_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@ def initialize(
)
@resource_repository = resource_repository
@data_object = data_object
@contract = "#{data_object}Contract".constantize.new(utils: ::Util::ContractUtils)
@logger = logger
end

def find_all(type:, account:)
@resource_repository.where(
Sequel.like(
:resource_id,
"#{account}:webservice:conjur/#{type}/%"
)
).all.map do |webservice|
authenticator_webservices(type: type, account: account).map do |webservice|
service_id = service_id_from_resource_id(webservice.id)

# Querying for the authenticator webservice above includes the webservices
# for the authenticator status. The filter below removes webservices that
# don't match the authenticator policy.
next unless webservice.id.split(':').last == "conjur/#{type}/#{service_id}"

load_authenticator(account: account, service_id: service_id, type: type)
begin
load_authenticator(account: account, service_id: service_id, type: type)
rescue => e
@logger.info("failed to load #{type} authenticator '#{service_id}' do to validation failure: #{e.message}")
nil
end
end.compact
end

Expand All @@ -36,7 +32,9 @@ def find(type:, account:, service_id:)
"#{account}:webservice:conjur/#{type}/#{service_id}"
)
).first
return unless webservice
unless webservice
raise Errors::Authentication::Security::WebserviceNotFound, "#{type}/#{service_id}"
end

load_authenticator(account: account, service_id: service_id, type: type)
end
Expand All @@ -47,6 +45,20 @@ def exists?(type:, account:, service_id:)

private

def authenticator_webservices(type:, account:)
@resource_repository.where(
Sequel.like(
:resource_id,
"#{account}:webservice:conjur/#{type}/%"
)
).all.select do |webservice|
# Querying for the authenticator webservice above includes the webservices
# for the authenticator status. The filter below removes webservices that
# don't match the authenticator policy.
webservice.id.split(':').last.match?(%r{^conjur/#{type}/[\w\-_]+$})
end
end

def service_id_from_resource_id(id)
full_id = id.split(':').last
full_id.split('/')[2]
Expand All @@ -59,26 +71,32 @@ def load_authenticator(type:, account:, service_id:)
"#{account}:variable:conjur/#{type}/#{service_id}/%"
)
).eager(:secrets).all

args_list = {}.tap do |args|
args[:account] = account
args[:service_id] = service_id
variables.each do |variable|
next unless variable.secret

args[variable.resource_id.split('/')[-1].underscore.to_sym] = variable.secret.value
# If variable exists but does not have a secret, set the value to an empty string.
# This is used downstream for validating if a variable has been set or not, and thus,
# what error to raise.
value = variable.secret ? variable.secret.value : ''
args[variable.resource_id.split('/')[-1].underscore.to_sym] = value
end
end

begin
allowed_args = %i[account service_id] +
@data_object.const_get(:REQUIRED_VARIABLES) +
@data_object.const_get(:OPTIONAL_VARIABLES)
args_list = args_list.select { |key, value| allowed_args.include?(key) && value.present? }
@data_object.new(**args_list)
rescue ArgumentError => e
@logger.debug("DB::Repository::AuthenticatorRepository.load_authenticator - exception: #{e}")
nil
# Validate the variables against the authenticator contract
result = @contract.call(args_list)
if result.success?
@data_object.new(**result.to_h)
else
@logger.info(result.errors.to_h.inspect)

# If contract fails, raise the first defined exception...
error = result.errors.first
raise(error.meta[:exception]) if error.meta[:exception].present?

# Otherwise, it's a validation error so raise the appropriate exception
raise(Errors::Conjur::RequiredSecretMissing,
"#{account}:variable:conjur/#{type}/#{service_id}/#{error.path.first.to_s.dasherize}")
end
end
end
Expand Down
Loading