Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose OIDC as standalone policy #904

Merged
merged 3 commits into from
Oct 1, 2018
Merged

Expose OIDC as standalone policy #904

merged 3 commits into from
Oct 1, 2018

Conversation

mikz
Copy link
Contributor

@mikz mikz commented Sep 19, 2018

We would like to use OIDC without 3scale, so we need a standalone OIDC policy for authentication.

Requirement for https://github.com/3scale/ostia/issues/14

/cc @3scale/ostia

@mikz mikz requested a review from a team as a code owner September 19, 2018 14:26
@mikz mikz force-pushed the oidc-policy branch 2 times, most recently from 226f5d9 to 89d58ba Compare September 27, 2018 09:32
It makes it way eaiser to verify the return status in the tests.
@mikz mikz force-pushed the oidc-policy branch 2 times, most recently from 0f1656f to 8031825 Compare September 27, 2018 12:27
@mikz mikz changed the title [wip] expose OIDC as standalone policy Expose OIDC as standalone policy Sep 27, 2018
@@ -15,6 +15,9 @@ local _M = { }

local mt = { __index = _M }

-- to be overridden in tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Can't we just use the http_backend that .new() receives?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that when you use this for example in the policy initializer, then the policy should accept this too. Pushing testing client through all levels of the stack seems wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is only for testing, another option would be to modify self.backend in the tests. Not sure if better.

I get your point of having to pass this through multiple objects.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this got me thinking. I think we should offer this in http_ng, so we can stub it globally in the whole test suite.

@@ -51,7 +51,7 @@ function _M.new(service)
jwt_claims = {
-- 1. The JWT MUST contain an "iss" (issuer) claim that contains a
-- unique identifier for the entity that issued the JWT.
iss = jwt_validators.equals_any_of({ issuer }),
iss = issuer and jwt_validators.equals_any_of({ issuer }) or jwt_validators.required(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is or jwt_validators.required() needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, I want to validate the issuer, if there is some, or just require the field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so is it OK if iss is there but not included in issuer ? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's the thing. If there is no issuer in the configuration, what to do?

I decided to just require any value for iss.

end

function _M:parse(jwt, cache_key)
local cached = self.cache:get(cache_key or jwt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why cache_key or jwt ? Shouldn't it be just cache_key ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because cache_key can be nil. So the token itself is its cache key.


local tostring = tostring

_M.cache_size = 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be exposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other modules to this too. I think it is reasonable. If you want to you could write a policy that in the init phase bumps cache sizes of other policies.

@@ -0,0 +1,91 @@
-- This is a oidc_authentication description.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting 👀

local self = new(config)

self.issuer_endpoint = valid_issuer_endpoint(config and config.issuer_endpoint)
self.discovery = oidc_discovery.new(self.http_backend)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't self.http_backend always nil at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unless you set it on the module level. self already has the metatable pointing to _M.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


describe('oidc_authentication policy', function()
describe('.new', function()
it('works without configuration', function()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these 2 cases with empty or nil config, it might be useful to check that rewrite and access do not raise unexpected errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

body = [[
{
"issuer": "https://example.com/issuer",
"jwks_uri": "https://example.com/jwks",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that extracting this one to a local var used in both mocks would nicely show how they're related.


policy:rewrite(context)

assert.contains({ payload = { iss = 'http://example.com/issuer' } }, context.jwt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return the other claims? I don't see why it only returns the iss.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. But the assert.contains verifies just subset. I could extend it to all the claims.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do that. It's a bit confusing in my opinion right now.
How about assert.same(access_token.payload, context.jwt) ?

ngx.var = {}
end)

it('parses access token', function()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also mention that it stores the parsed token in the context.
I think this is important because other policies might want to check if there's already a parsed jwt in there.

@davidor
Copy link
Contributor

davidor commented Oct 1, 2018

Shouldn't this policy have an apicast-policy.json ?

@mikz
Copy link
Contributor Author

mikz commented Oct 1, 2018

That would show it in the UI. I'm not sure we are ready for that. What would happen when a customer would add this policy to existing OIDC service? IMO we should think a bit more how that should work before exposing it.

local _M = require('apicast.policy.oidc_authentication')
local JWT = require('resty.jwt')
local rsa = require('fixtures.rsa')
local oidc_discovery = require('resty.oidc.discovery')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'oidc_discovery'

local stub = require('luassert.stub')
local stubbed

busted.before_each(function(...)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable length argument

@mikz mikz merged commit ab4ce9d into master Oct 1, 2018
@mikz mikz deleted the oidc-policy branch October 1, 2018 16:16
@davidor davidor added this to the 3.4 milestone Oct 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants