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

feat: Add filter on HTTP methods for consumer-restriction plugin #3691

Merged
merged 7 commits into from
Mar 8, 2021
Merged

feat: Add filter on HTTP methods for consumer-restriction plugin #3691

merged 7 commits into from
Mar 8, 2021

Conversation

jp-gouin
Copy link
Contributor

What this PR does / why we need it:

Fix for #3635
Add the possibility to add allowed_methods for a user when whitelist is set.
This will restrict the user to only performed the HTTP action matching the list specified in allowed_method.methods

This is the format :

"plugins": {
        "key-auth": {},
        "consumer-restriction": {
            "whitelist": [
                "jack1"
            ],
            "allowed_methods":[{
              "user": "jack1",
              "methods": ["POST"]
            }]
        }
    }

I choose to add a dedicated section instead of modifying the existing whitelist one because i think it's more readable.
When nothing is set , then only the whitelist is applied .
And in order to set allowed_methods , whitelist is required.

Pre-submission checklist:

Test case updated with 2 basics tests

  1. only allow post on resources and try to get the resource -> unauthorized
  2. Add getcapability to the user and try to get the resource -> authorized

Add 2 test cases for testing the plugin scheme

  1. Only allowed_methods is set -> failed
  2. allowed_methods is set with blacklist -> failed

English documentation updated , any help for the Chineese one would be appreciated.

  • Did you explain what problem does this PR solve? Or what new features have been added?
  • Have you added corresponding test cases?
  • Have you modified the corresponding document?
  • Is this PR backward compatible? If it is not backward compatible, please discuss on the mailing list first

@jp-gouin jp-gouin changed the title Add filter on HTTP methods for consumer-restriction plugin feat: Add filter on HTTP methods for consumer-restriction plugin Feb 26, 2021
@@ -114,12 +161,16 @@ function _M.access(conf, ctx)
if not is_include(value, conf.whitelist) then
block = true
end
if conf.allowed_methods and #conf.allowed_methods > 0 then
if not is_method_allow(conf.allowed_methods, method, value) then
Copy link
Member

Choose a reason for hiding this comment

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

"is_method_allowed"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree

{
title = "allowed_methods",
properties = {
type = {
Copy link
Member

Choose a reason for hiding this comment

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

Can we extra this part and share across the components with different titles?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I'm not sure to understand what you mean by extra

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, it is my typo. It should be extract.

@@ -53,6 +53,38 @@ local schema = {
rejected_code = {type = "integer", minimum = 200, default = 403}
},
required = {"whitelist"},
},
{
title = "allowed_methods",
Copy link
Member

Choose a reason for hiding this comment

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

Would it be better to use allowed_by_methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree

"plugins": {
"basic-auth": {},
"consumer-restriction": {
"whitelist": [
Copy link
Member

Choose a reason for hiding this comment

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

        "consumer-restriction": {
            "allowed_by_methods":[{
                "user": "jack1",
                "methods": ["POST"]
            }]
        }

is enough. There is no need to configure jack1 in two places. And the allow already has the meaning of whitelist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok so i completely drop the whitelist part in favor of allowed_by_methods ?

Copy link
Contributor Author

@jp-gouin jp-gouin Mar 2, 2021

Choose a reason for hiding this comment

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

If so that would mean some rework of the whole plugin.
Right now the allowed_by_methods required to set the list of methods , so if we drop whitelistwe need a quick setup . Like if there is no method , then full access ? or the user still has to set the list of method but we take into account a * wildcard ?

 "consumer-restriction": {
            "allowed_by_methods":[{
                "user": "jack1",
                "methods": []   ---> Full access or full restriction ?
            }]
        }
 "consumer-restriction": {
            "allowed_by_methods":[{
                "user": "jack1",
                "methods": ["*"]   ---> Full access instead of putting the full list ?
            }]
        }

Copy link
Member

Choose a reason for hiding this comment

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

I think we can use the schema below to allow both whitelist and allowed_by_methods:

local schema = {
    type = "object",
    properties = {
        type = {
            type = "string",
            enum = {"consumer_name", "service_id"},
            default = "consumer_name"
        },
        blacklist = {
            type = "array",
            minItems = 1,
            items = {type = "string"}
        },
        whitelist = {
            type = "array",
            minItems = 1,
            items = {type = "string"}
        },
        allowed_by_methods = {
            type = "array",
            items = {
                type = "object",
                properties = {
                    user = {
                        type = "string"
                    },
                    methods = {
                        type = "array",
                        minItems = 1,
                        items = {
                            type = "string",
                            enum = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD",
                                    "OPTIONS", "CONNECT", "TRACE"},
                        }
                    }
                }
            }
        },
        rejected_code = {type = "integer", minimum = 200, default = 403}
    },
    anyOf = {
        {required = {"blacklist"}},
        {required = {"whitelist"}},
        {required = {"allowed_by_methods"}}
    },
}

Copy link
Member

Choose a reason for hiding this comment

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

We can change the title of

=== TEST 2: whitelist and blacklist mutual exclusive
and say that the priority is blacklist > whitelist > allowed_by_methods.

Copy link
Member

Choose a reason for hiding this comment

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

It is a large change but I think it is flexible for the users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok , no problem.
So allowed_by_methods could exist without whitelist.
If whitelist is set it will override what could be specified in the allowed_by_methods section

@@ -16,7 +16,7 @@
--
local ipairs = ipairs
local core = require("apisix.core")

local ngx = ngx
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 it is better to align with the above format, for example:

local ipairs    = ipairs
local core      = require("apisix.core")
local ngx       = ngx

@@ -84,20 +116,35 @@ local function is_include(value, tab)
return false
end

local function is_method_allow(allowed_methods, method, user)
for key,value in ipairs(allowed_methods) do
Copy link
Contributor

Choose a reason for hiding this comment

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

There should be a space after ,, and key is not used, it should be replaced with a placeholder. like this:

for _, value in ipairs(allowed_methods) do

local function is_method_allow(allowed_methods, method, user)
for key,value in ipairs(allowed_methods) do
if value.user == user then
for k,allowed_method in ipairs(value.methods) do
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto.

…hod_allow to is_method_allowed, add indentation for import. replace unused key in pairs and ipairs call by _
…e all set , the priority is blacklist > whitelist > allowed_by_methods. update test case accordingly and update the documentation with this new behavior
@jp-gouin
Copy link
Contributor Author

jp-gouin commented Mar 4, 2021

I made the following update :
blacklist , whitelist and allowed_by_methods can be set at the same time.
blacklist has the priority over whitelist and whitelist has the priority over allowed_by_methods.

There is now a direct reject of the connection if the user is blacklisted

if conf.blacklist and #conf.blacklist > 0 then
        if is_include(value, conf.blacklist) then
            return reject(conf)
        end
    end

allowed_by_methods is only performed if the user is not whitelisted

 if conf.allowed_by_methods and #conf.allowed_by_methods > 0 and not whitelisted then
        if not is_method_allowed(conf.allowed_by_methods, method, value) then
            block = true
        end
    end

The overall behavior stay the same is you set only blacklist and or whitelist




=== TEST 28: test blacklist priority
Copy link
Member

Choose a reason for hiding this comment

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

test whitelist priority

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes my bad

@@ -475,7 +474,7 @@ Authorization: Basic amFjazIwMjA6MTIzNDU2



=== TEST 21: remove consumer-restriction
Copy link
Member

Choose a reason for hiding this comment

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

Why change this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I bump the remove restriction to test 31 due to the tests i added and to remains consistent .

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.

4 participants