Skip to content

Commit

Permalink
Authn OIDC - Draft #1 (#736)
Browse files Browse the repository at this point in the history
This is draft #1 of a working OIDC authenticator.  Plans to refactor its API are in the works.
  • Loading branch information
moticless authored and jonahx committed Oct 3, 2018
1 parent 8279f9a commit 80fb3ec
Show file tree
Hide file tree
Showing 34 changed files with 1,757 additions and 58 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ ci/authn-k8s/dev/*.yaml
ci/authn-k8s/dev/policies/*.yml
!ci/authn-k8s/dev/policies/*template*

# authn-oidc phantomjs temp files
dev/files/authn-oidc/phantomjs/authorization_code
dev/files/authn-oidc/phantomjs/keycloak_login_tmp.js
16 changes: 16 additions & 0 deletions Dockerfile.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@ ARG VERSION=latest
FROM conjur:${VERSION}

RUN bundle --no-deployment --without ''

RUN echo Installing phantomjs
RUN apt-get update -y && \
apt-get install -y build-essential chrpath libssl-dev libxft-dev && \
apt-get install -y libfreetype6 libfreetype6-dev && \
apt-get install -y libfontconfig1 libfontconfig1-dev wget

RUN cd ~
ENV PHANTOM_JS="phantomjs-1.9.8-linux-x86_64"
RUN wget https://bitbucket.org/ariya/phantomjs/downloads/${PHANTOM_JS}.tar.bz2
RUN tar xvjf $PHANTOM_JS.tar.bz2

RUN mv $PHANTOM_JS /usr/local/share
RUN ln -sf /usr/local/share/${PHANTOM_JS}/bin/phantomjs /usr/local/bin

RUN phantomjs --version
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ end
gem 'kubeclient'
gem 'websocket-client-simple'

# authn-oidc
gem 'openid_connect'

group :development, :test do
gem 'aruba'
gem 'csr'
Expand Down
38 changes: 38 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ GEM
tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.0.1)
arel (6.0.4)
aruba (0.14.2)
childprocess (~> 0.5.6)
Expand All @@ -64,6 +65,7 @@ GEM
rspec-expectations (>= 2.99)
thor (~> 0.19)
ast (2.4.0)
attr_required (1.0.1)
autoprefixer-rails (7.1.2.3)
execjs
aws-eventstream (1.0.1)
Expand All @@ -84,6 +86,7 @@ GEM
base32-crockford (0.1.0)
base58 (0.2.3)
bcrypt-ruby (3.0.1)
bindata (2.4.3)
bootstrap-sass (3.2.0.2)
sass (~> 3.2)
builder (3.2.3)
Expand Down Expand Up @@ -194,6 +197,7 @@ GEM
domain_name (~> 0.5)
http-form_data (1.0.3)
http_parser.rb (0.6.0)
httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
Expand All @@ -202,6 +206,10 @@ GEM
jaro_winkler (1.5.1)
jmespath (1.4.0)
json (2.1.0)
json-jwt (1.9.4)
activesupport
aes_key_wrap
bindata
json_spec (1.1.5)
multi_json (~> 1.0)
rspec (>= 2.0, < 4.0)
Expand Down Expand Up @@ -232,6 +240,16 @@ GEM
netrc (0.11.0)
nokogiri (1.8.4)
mini_portile2 (~> 2.3.0)
openid_connect (1.1.6)
activemodel
attr_required (>= 1.0.0)
json-jwt (>= 1.5.0)
rack-oauth2 (>= 1.6.1)
swd (>= 1.0.0)
tzinfo
validate_email
validate_url
webfinger (>= 1.0.1)
parallel (1.12.1)
parser (2.5.1.2)
ast (~> 2.4.0)
Expand All @@ -249,6 +267,12 @@ GEM
public_suffix (3.0.2)
puma (3.12.0)
rack (1.6.10)
rack-oauth2 (1.9.2)
activesupport
attr_required
httpclient
json-jwt (>= 1.9.0)
rack
rack-rewrite (1.5.1)
rack-test (0.6.3)
rack (>= 1.0)
Expand Down Expand Up @@ -372,6 +396,10 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
swd (1.1.2)
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
table_print (1.5.6)
therubyracer (0.12.3)
libv8 (~> 3.16.14.15)
Expand All @@ -387,11 +415,20 @@ GEM
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.4.0)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.2)
activemodel (>= 3.0.0)
addressable
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
webfinger (1.1.0)
activesupport
httpclient (>= 2.4)
websocket (1.2.8)
websocket-client-simple (0.3.0)
event_emitter
Expand Down Expand Up @@ -432,6 +469,7 @@ DEPENDENCIES
listen
net-ldap
nokogiri (>= 1.8.2)
openid_connect
parallel
pg
pry-byebug
Expand Down
63 changes: 39 additions & 24 deletions app/controllers/authenticate_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
class AuthenticateController < ApplicationController
include BasicAuthenticator

def index
def index
authenticators = {
# Installed authenticator plugins
installed: installed_authenticators.keys.sort,

# Authenticator webservices created in policy
configured: configured_authenticators.sort,

Expand All @@ -25,14 +25,37 @@ def login
end

def authenticate
authn_token = authentication_strategy.conjur_token(authentication_input)
authn_token = authentication_strategy.conjur_token(
::Authentication::Strategy::Input.new(
authenticator_name: params[:authenticator],
service_id: params[:service_id],
account: params[:account],
username: params[:id],
password: request.body.read,
origin: request.ip,
request: request
)
)
render json: authn_token
rescue => e
logger.debug("Authentication Error: #{e.message}")
e.backtrace.each do |line|
logger.debug(line)
end
raise Unauthorized
handle_authentication_error(e)
end

def authenticate_oidc
authentication_token = authentication_strategy.conjur_token_oidc(
::Authentication::Strategy::Input.new(
authenticator_name: 'authn-oidc',
service_id: params[:service_id],
account: params[:account],
username: nil,
password: nil, # TODO: Remove once we seperate oidc Strategy
origin: request.ip,
request: request
)
)
render json: authentication_token
rescue => e
handle_authentication_error(e)
end

def k8s_inject_client_cert
Expand All @@ -44,15 +67,19 @@ def k8s_inject_client_cert
)
head :ok
rescue => e
logger.debug("Authentication Error: #{e.message}")
e.backtrace.each do |line|
handle_authentication_error(e)
end

private

def handle_authentication_error(err)
logger.debug("Authentication Error: #{err.message}")
err.backtrace.each do |line|
logger.debug(line)
end
raise Unauthorized
end

private

def authentication_strategy
@authentication_strategy ||= ::Authentication::Strategy.new(
authenticators: installed_authenticators,
Expand All @@ -64,18 +91,6 @@ def authentication_strategy
)
end

def authentication_input
::Authentication::Strategy::Input.new(
authenticator_name: params[:authenticator],
service_id: params[:service_id],
account: params[:account],
username: params[:id],
password: request.body.read,
origin: request.ip,
request: request
)
end

def installed_authenticators
@installed_authenticators ||= ::Authentication::InstalledAuthenticators.authenticators(ENV)
end
Expand Down
90 changes: 90 additions & 0 deletions app/domain/authentication/authn_oidc/authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require 'command_class'

module Authentication
module AuthnOidc
class AuthenticationError < RuntimeError; end
class NotFoundError < RuntimeError; end
class OIDCConfigurationError < RuntimeError; end
class OIDCAuthenticationError < RuntimeError; end

# TODO: Should really have a verb name "Authenticate" since it's a command
# object but we'll leave it like this for now for consistency
#
Authenticator = CommandClass.new(
dependencies: { env: ENV },
inputs: %i(input user_details)
) do

def call
validate_id_token_claims
validate_user_info
true
end

private

def validate_id_token_claims
expected = { client_id: client_id, issuer: issuer } # , nonce: 'nonce'}
@user_details.id_token.verify!(expected)
end

def validate_user_info
raise OIDCAuthenticationError, subject_err_msg unless valid_subject?
raise OIDCAuthenticationError, no_profile_err_msg unless preferred_username
end

def user_info
@user_details.user_info
end

def valid_subject?
user_info.sub == id_token_subject
end

def preferred_username
user_info.preferred_username
end

def client_id
@user_details.client_id
end

def issuer
@user_details.issuer
end

def id_token_subject
@user_details.id_token.sub
end

def authenticator_name
@input.authenticator_name
end

def service_id
@input.service_id
end

def conjur_account
@input.account
end

def request_body
@request_body ||= @input.request.body.read
end

def service
@service ||= Resource["#{conjur_account}:webservice:conjur/#{authenticator_name}/#{service_id}"]
end

def no_profile_err_msg
"[profile] is not included in scope of authorization code request"
end

def subject_err_msg
"User info subject [#{user_info.sub}] and id token subject " +
"[#{id_token_subject}] are not equal"
end
end
end
end
Loading

0 comments on commit 80fb3ec

Please sign in to comment.