Skip to content

Commit

Permalink
OIDC V2: Extended token TTL POC
Browse files Browse the repository at this point in the history
  • Loading branch information
john-odonnell committed Dec 5, 2022
1 parent fd6155b commit e022d8c
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 16 deletions.
22 changes: 19 additions & 3 deletions app/db/repository/authenticator_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ def find_all(type:, account:)
"#{account}:webservice:conjur/#{type}/%"
)
).all.map do |webservice|
load_authenticator(account: account, id: webservice.id.split(':').last, type: type)
load_authenticator(
account: account,
id: webservice.id.split(':').last,
type: type,
custom_ttl: get_custom_token_ttl_from_annotation(webservice: webservice)
)
end.compact
end

Expand All @@ -27,7 +32,12 @@ def find(type:, account:, service_id:)
).first
return unless webservice

load_authenticator(account: account, id: webservice.id.split(':').last, type: type)
load_authenticator(
account: account,
id: webservice.id.split(':').last,
type: type,
custom_ttl: get_custom_token_ttl_from_annotation(webservice: webservice)
)
end

def exists?(type:, account:, service_id:)
Expand All @@ -36,7 +46,12 @@ def exists?(type:, account:, service_id:)

private

def load_authenticator(type:, account:, id:)
def get_custom_token_ttl_from_annotation(webservice:)
token_ttl_key = @data_object.const_get(:TOKEN_TTL_ANNOTATION_KEY)
webservice.annotation(token_ttl_key)
end

def load_authenticator(type:, account:, id:, custom_ttl:)
service_id = id.split('/')[2]
variables = @resource_repository.where(
Sequel.like(
Expand All @@ -48,6 +63,7 @@ def load_authenticator(type:, account:, id:)
args_list = {}.tap do |args|
args[:account] = account
args[:service_id] = service_id
args[:token_ttl] = custom_ttl if custom_ttl
variables.each do |variable|
next unless variable.secret

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,24 @@ module DataObjects
class Authenticator

REQUIRED_VARIABLES = %i[provider_uri client_id client_secret claim_mapping].freeze
OPTIONAL_VARIABLES = %i[redirect_uri response_type provider_scope name].freeze
OPTIONAL_VARIABLES = %i[redirect_uri response_type provider_scope name token_ttl].freeze

attr_reader :provider_uri, :client_id, :client_secret, :claim_mapping, :account
attr_reader :service_id, :redirect_uri, :response_type

# Constants that all OIDC V2 Authenticator instances are subject to.
# Annotation values must be a properly formatted ISO 8601 Duration.
# For example, the following annotation key/value pair will
# configure the AuthnOIDC webservice to distribute tokens that are
# valid for 30 minutes:
# token/ttl: 'PT30M'
# With these constants defined here, different Authn flavors can be
# subject to unique restrictions.
TOKEN_TTL_ANNOTATION_KEY = 'token/ttl'
TOKEN_TTL_MAX = 5.hours
TOKEN_TTL_MIN = 8.minutes
TOKEN_TTL_DEFAULT = TOKEN_TTL_MIN

def initialize(
provider_uri:,
client_id:,
Expand All @@ -20,7 +33,8 @@ def initialize(
redirect_uri: nil,
name: nil,
response_type: 'code',
provider_scope: nil
provider_scope: nil,
token_ttl: nil
)
@account = account
@provider_uri = provider_uri
Expand All @@ -32,6 +46,7 @@ def initialize(
@name = name
@provider_scope = provider_scope
@redirect_uri = redirect_uri
@token_ttl = token_ttl
end

def scope
Expand All @@ -45,6 +60,26 @@ def name
def resource_id
"#{account}:webservice:conjur/authn-oidc/#{service_id}"
end

# Returns the validity duration, in seconds, of an instance's access tokens.
def token_ttl
return TOKEN_TTL_DEFAULT unless @token_ttl

begin
duration = ActiveSupport::Duration.parse(@token_ttl) if @token_ttl
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
raise Errors::Authentication::DataObjects::InvalidTokenTTL.new(resource_id, @token_ttl)
end

case
when duration < TOKEN_TTL_MIN
return TOKEN_TTL_MIN
when duration > TOKEN_TTL_MAX
return TOKEN_TTL_MAX
else
return duration
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ class Authenticator
attr_reader :provider_uri, :client_id, :client_secret, :claim_mapping, :nonce, :state, :account
attr_reader :service_id, :redirect_uri, :response_type

# Constants that all OIDC V2 Authenticator instances are subject to.
# Annotation values must be a properly formatted ISO 8601 Duration.
# For example, the following annotation key/value pair will
# configure the AuthnOIDC webservice to distribute tokens that are
# valid for 30 minutes:
# token/ttl: 'PT30M'
# With these constants defined here, different Authn flavors can be
# subject to unique restrictions.
TOKEN_TTL_ANNOTATION_KEY = 'token/ttl'
TOKEN_TTL_MAX = 5.hours
TOKEN_TTL_MIN = 8.minutes
TOKEN_TTL_DEFAULT = TOKEN_TTL_MIN

def initialize(
provider_uri:,
client_id:,
Expand All @@ -20,7 +33,8 @@ def initialize(
redirect_uri: nil,
name: nil,
response_type: 'code',
provider_scope: nil
provider_scope: nil,
token_ttl: nil
)
@account = account
@provider_uri = provider_uri
Expand All @@ -34,6 +48,7 @@ def initialize(
@name = name
@provider_scope = provider_scope
@redirect_uri = redirect_uri
@token_ttl = token_ttl
end

def scope
Expand All @@ -47,6 +62,26 @@ def name
def resource_id
"#{account}:webservice:conjur/authn-oidc/#{service_id}"
end

# Returns the validity duration, in seconds, of an instance's access tokens.
def token_ttl
return TOKEN_TTL_DEFAULT unless @token_ttl

begin
duration = ActiveSupport::Duration.parse(@token_ttl) if @token_ttl
rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
raise Errors::Authentication::DataObjects::InvalidTokenTTL.new(resource_id, @token_ttl)
end

case
when duration < TOKEN_TTL_MIN
return TOKEN_TTL_MIN
when duration > TOKEN_TTL_MAX
return TOKEN_TTL_MAX
else
return duration
end
end
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion app/domain/authentication/handler/authentication_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def call(parameters:, request_ip:)

TokenFactory.new.signed_token(
account: parameters[:account],
username: role.role_id.split(':').last
username: role.role_id.split(':').last,
user_ttl: authenticator.token_ttl
)
rescue => e
log_audit_failure(parameters[:account], parameters[:service_id], request_ip, @authenticator_type, e)
Expand Down
9 changes: 9 additions & 0 deletions app/domain/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ module Authentication
code: "CONJ00126E"
)

module DataObjects

InvalidTokenTTL = ::Util::TrackableErrorClass.new(
msg: "Webservice '{0-webservice}' configured with invalid custom token TTL '{1-ttl}': must be a valid duration as described by ISO 8601",
code: "CONJ00134E"
)

end

module AuthenticatorClass

DoesntStartWithAuthn = ::Util::TrackableErrorClass.new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def username
payload['sub']
end

def duration
payload['exp'] - payload['iat']
end

private

def payload
Expand Down
48 changes: 45 additions & 3 deletions cucumber/authenticators_oidc/features/authn_oidc_v2.feature
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator
body:
- !webservice
annotations:
description: Authentication service for Keycloak, based on Open ID Connect.
description: Authentication service for Keycloak, based on Open ID Connect. Uses the default token TTL of 8 minutes.
- !variable name
- !variable provider-uri
- !variable response-type
Expand All @@ -30,31 +30,73 @@ Feature: OIDC Authenticator V2 - Users can authenticate with OIDC authenticator
role: !group users
privilege: [ read, authenticate ]
resource: !webservice
- !policy
id: conjur/authn-oidc/keycloak2-long-lived
body:
- !webservice
annotations:
description: Authentication service for Keycloak, based on Open ID Connect. Uses a 4 hour token TTL.
token/ttl: PT4H
- !variable name
- !variable provider-uri
- !variable response-type
- !variable client-id
- !variable client-secret
- !variable claim-mapping
- !variable state
- !variable nonce
- !variable redirect-uri
- !variable provider-scope
- !group users
- !permit
role: !group users
privilege: [ read, authenticate ]
resource: !webservice
- !user
id: alice
- !grant
role: !group conjur/authn-oidc/keycloak2/users
member: !user alice
- !grant
role: !group conjur/authn-oidc/keycloak2-long-lived/users
member: !user alice
"""
And I am the super-user
And I successfully set OIDC V2 variables for "keycloak2"
And I successfully set OIDC V2 variables for "keycloak2-long-lived"

@smoke
Scenario: A valid code to get Conjur access token
Scenario: A valid code to get Conjur access token from webservice with default token TTL
# We want to verify the returned access token is valid for retrieving a secret
Given I have a "variable" resource called "test-variable"
And I permit user "alice" to "execute" it
And I add the secret value "test-secret" to the resource "cucumber:variable:test-variable"
And I fetch a code for username "alice" and password "alice"
And I save my place in the audit log file
When I authenticate via OIDC V2 with code
Then user "alice" has been authorized by Conjur
Then user "alice" has been authorized by Conjur for 8 minutes
And I successfully GET "/secrets/cucumber/variable/test-variable" with authorized user
And The following appears in the audit log after my savepoint:
"""
cucumber:user:alice successfully authenticated with authenticator authn-oidc service cucumber:webservice:conjur/authn-oidc/keycloak2
"""

Scenario: A valid code to get Conjur access token from webservice with custom token TTL
Given I have a "variable" resource called "test-variable"
And I permit user "alice" to "execute" it
And I add the secret value "test-secret" to the resource "cucumber:variable:test-variable"
And I fetch a code for username "alice" and password "alice"
And I save my place in the audit log file
When I authenticate via OIDC V2 with code and service-id "keycloak2-long-lived"
Then user "alice" has been authorized by Conjur for 4 hours
And I successfully GET "/secrets/cucumber/variable/test-variable" with authorized user
And The following appears in the audit log after my savepoint:
"""
cucumber:user:alice successfully authenticated with authenticator authn-oidc service cucumber:webservice:conjur/authn-oidc/keycloak2-long-lived
"""

@smoke
Scenario: A valid code with email as claim mapping
Given I extend the policy with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@
expect(retrieved_access_token.username).to eq(username)
end

Then(/^user "(\S+)" has been authorized by Conjur for (\d+) hours$/) do |username, duration_hours|
token = retrieved_access_token
expect(token.username).to eq(username)
expect(token.duration).to eq(duration_hours * 60 * 60)
end

Then(/^user "(\S+)" has been authorized by Conjur for (\d+) minutes$/) do |username, duration_minutes|
token = retrieved_access_token
expect(token.username).to eq(username)
expect(token.duration).to eq(duration_minutes * 60)
end

When(/^I authenticate via OIDC with id token and without a service-id$/) do
authenticate_id_token_with_oidc(
service_id: nil,
Expand Down
9 changes: 8 additions & 1 deletion spec/app/db/repository/authenticator_repository_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
::Role.create(
role_id: "rspec:policy:conjur/authn-oidc/abc123"
)
::Resource.create(
@authn_resource = ::Resource.create(
resource_id: "rspec:webservice:conjur/authn-oidc/abc123",
owner_id: "rspec:policy:conjur/authn-oidc/abc123"
)
Expand Down Expand Up @@ -171,6 +171,13 @@
it { expect(authenticator).to be_kind_of(data_object) }
it { expect(authenticator.account).to eq('rspec') }
it { expect(authenticator.service_id).to eq('abc123') }

context 'when token TTL is set through annotations' do
before(:each) do
::Annotation.create(resource: @authn_resource, name: 'token/ttl', value: 'PT2H')
end
it { expect(authenticator.token_ttl).to eq(2.hours)}
end
end

after(:each) do
Expand Down
Loading

0 comments on commit e022d8c

Please sign in to comment.