-
Notifications
You must be signed in to change notification settings - Fork 170
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
SOAP policy #567
SOAP policy #567
Conversation
local soap_usage = context.service:get_usage( | ||
ngx.req.get_method(), soap_action_uri) | ||
|
||
context.add_to_usage = soap_usage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikz I considered several options here but this seemed the cleanest one. We could pass the URI instead and do the mapping rules matching later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how several policies would interact. Or if this would be twice in the chain.
My idea was to make each policy to increment the values in the context. That way the policy can decide what to do: add, replace, remove, ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. If in the future we have more policies doing the same thing, we'll need to update add_to_usage
instead of simply assign it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean is that we can just context.usage.hits = (context.usage.hits or 0) + 1
in the rewrite phase. Or expose some table merger function to merge those. Or have them in an array and merge them in the proxy:access. It just does not feel right to have add_to_usage
when the usage
key is there too.
gateway/src/apicast/proxy.lua
Outdated
end | ||
end | ||
|
||
function _M:access(service) | ||
function _M:access(service, context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikz I decided to pass the context instead of passing just the usage to be increased because in the future there might be other policies passing other information in the context. This way we won't need to change the signature of the access() method again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. https://github.com/3scale/apicast/pull/556/files#diff-31313c92616b54028dac1ba183e6f79aR287 was all the info it needed. It is quite a lot of params, but not that bad imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with adding 2 or 3 extract params. I'm more concerned about changing the signature of access()
in the future and breaking backwards compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Maybe we can build on #556 and try to figure out out there and then just use it from here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good 👍
-- This policy adds support for a very small subset of SOAP. | ||
-- This policy basically expects a SOAPAction URI in the SOAPAction header or | ||
-- the content-type header. | ||
-- The SOAPAction header is used in v1.1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"....v1.1 of the SOAP Standard"
(just to avoid any confusion in readers about versions of apicast or something?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
-- https://www.w3.org/TR/soap12-part2/#ActionFeature | ||
-- The SOAPAction URI is matched against the mapping rules defined for the | ||
-- service and the usage resulting from that is authorized and reported | ||
-- against 3scale's backend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"...and reported to the API Management platform using the Service Management API" or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually refer to backend as '3scale's backend', sounds simpler than that, but would be good to reach an agreement on that. Should we call it by its new upstream name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call it 3scale's backend. Definitely try not to write its full name because I could not get it right and have to copy paste it from somehwere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed @mikz
local _M = policy.new('SOAP policy') | ||
|
||
local soap_action_header = 'SOAPAction' | ||
local soap_action_ctype = 'application/soap+xml?action=' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this has to be parsed as URI args. Because we can't control the action appears right after as it can have &
and be the last param.
Also quick google search shows some people use ;
to concat params. We should verify if that can really be the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right @mikz . I wrongly assumed that action
would be the only param.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About using ;
, I think we need to support that instead of ?...&...
:
https://developer.mozilla.org/es/docs/Web/HTTP/Headers/Content-Type and https://tools.ietf.org/html/rfc1341
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. https://tools.ietf.org/html/rfc7231#section-3.1.1.1 says parameters are separated by ;
and https://tools.ietf.org/html/rfc3902 says it is a parameter. I wonder where I've seen the example with ?
. Can't find it now.
|
||
if soap_action_uri then | ||
local soap_usage = context.service:get_usage( | ||
ngx.req.get_method(), soap_action_uri) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is using service mapping rules? I'm not sure users can define full URLs there.
I though we will have some mapping rules in this policy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed it was possible to define something like /a_path#some_action
in the mapping rules. That's why I decided to do it this way. I see now that it's not possible. With that constraint, I think we need to include mapping rules in the configuration of this policy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikz Let me know what you think about this solution:
- Add a mapping rules array in the policy config. Each element will contain:
pattern
,http_method
,metric_name
, anddelta
. The same fields we have for the service mapping rules. - Add a
add_mapping_rule()
method inService
. This would simply accept the fields above, add the ones needed to construct the rule, and then add it to theself.rules
table. We'll need to make sure thatadd_mapping_rule()
is only called once during the lifetime of the policy so we don't add duplicated rules. - The above will let us call the existing
Service:get_usage()
. This method returns the metrics to auth and report based on the mapping rules defined. We'll call this method only when we receive a SOAP action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidor interesting idea. I'm not sure if I like the idea of merging then into one list. I think they should be applied separately. IMO there could be some rules that match both and because some are absolute and some relative it could be quite nasty as /
would increment hits all the time and twice.
Maybe we need to exposeUsageExtractor:get_usage
which is initialized with list of mapping rules. Basically exposing the low level API where you can inject own mapping rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get your first point, but wouldn't that be coherent with the way mapping rules work currently?
Suppose that the policy adds this mapping rule: /a_path#a_soap_action => +1 a_soap
. If the user has already defined /a_path => +1 a_path
, we should increase both a_soap
and a_path
by 1. If the user already defined / => +1 hits
, and hits
is a parent metric of the other 2, well, it's going to be increased by 3(!), but that's how it currently works no?
Regarding your second suggestion about exposing UsageExtractor:get_usage
, could you please expand a bit on it or provide some pseudo-code or examples?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the issue is when you have following rules:
/ => hits
/a => a
And in soap you'll have
http://example.com/soap#a => a
If you match http://example.com/soap#a
it will return hits=1&a=1
.
So it depends if this replaces mapping rules or complements them.
If it would first match mapping rules and then soap rules and merged the results then for a request:
GET /
Content-Type: application/xml+soap?action=http://example.com/soap#a
it would match hits=2&a=1
. Which is wrong IMO. Because the path mapping rules should not be applied to soap mapping rules (because they are relative and not absolute).
I'm not thinking about parent/child metrics, just about applying mapping rules to the request path/ soap action.
The point is that current path relative mapping rules like /a
can unintentionally match soap services like http://example.com/a
and double counting. And also match different services like http://example.com/a
and http://foobar.com/a
.
And the signature of UsageExtractor would be quite simple.
local usage_rules = UsageExtractor.new({
{ pattern = 'http://example.com/a', http_method = 'GET', metric_name = 'a', delta = 1 }
})
local usage = usage_rules:get_usage(...)
And the Service:get_usage
would use this too. But we would just have an API to create own independent list of rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikz Thanks for the example.
It's clear to me now why you think that a url received via SOAP Action should not be matched against the service mapping rules. It should only be matched against the mapping rules specified in the SOAP policy. I agree with that.
Now, what happens when the SOAP policy is enabled but a request does not contain a SOAP Action ? I think we should apply the service mapping rules in that case.
I like the idea of extracting a UsageExtractor
module from Service
and Configuration
. This is something I've been thinking about. Those 2 modules have many responsabilities and need to be splitted. This seems to be a good excuse to do it :) I'll address this task in a separate PR.
ab3ec72
to
fd7c54f
Compare
@@ -0,0 +1 @@ | |||
return require('soap') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I started to think about this. And I think we can expose some support tooling in different module, so it can be unit tested.
Lets say the main policy code is in soap_policy.lua
. Then there can be soap.lua
that has stuff like "extracting the soap action" and it can be unit tested in busted.
That would allow us not exposing extra methods on the policy, but still exposing it internally (if the loading works) for tests. And policies should not be able to load other policies (but that is not enforced yet), so we should be fine and the code should be used only from tests.
Just some food for though. I'd like to hear your take.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be a nice improvement.
Otherwise, in some cases Proxy reports the usage associated with the service mapping rules instead of the merged one.
|
||
local function usage_from_matching_rules(soap_action_uri, rules) | ||
return mapping_rules_matcher.get_usage_from_matches( | ||
nil, soap_action_uri, {}, rules) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikz Notice that I'm sending http_method = nil here. Not sure if we should take it into account for SOAP actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really know about this. The Content-Type is included in the request, not in the SOAP action URI.
Depending on what we decide here we'll need to add it to the JSON schema also. Notice that for now, I only included 'pattern', 'metric', and 'delta', and left out other fields that are present in the proxy rules
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. We are initializing the mapping rules directly from the config. I was thinking that we could just override the http_method
attribute with POST
there. And then pass the real http method here. So we match it only when the request is POST.
But it is probably not really important.
} | ||
end | ||
|
||
soap_policy:rewrite(context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. This is so good 🥇 Great we can unit test this.
local function soap_action_in_ctype(headers) | ||
local ctype = headers['Content-Type'] | ||
|
||
if ctype and starts_with(ctype, soap_action_ctype) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this won't be that easy 🤔
The RFC says those are all equivalent:
text/html;charset=utf-8
text/html;charset=UTF-8
Text/HTML;Charset="utf-8"
text/html; charset="utf-8"
And looks like it can have whitespace before ;
too. Look at the definition: media-type = type "/" subtype *( OWS ";" OWS parameter )
. And OWS
is optional white space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check last commit.
local name = header_param_split[1] | ||
local value = header_param_split[2] | ||
if name == "action" then | ||
return value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value can be either "url"
or just url
. We should strip the quotes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also solved in the new commit.
local function soap_action_from_ctype_params(params) | ||
local params_split = re.split(params, ";") | ||
local params_without_blanks = ngx.re.gsub(params, '\\s', '') | ||
local params_split = re.split(params_without_blanks, ";") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could also split by [[\s*;\s*]]
to remove spaces around ;
, right?
Seems a bit evil to strip all spaces from it. What if there is a space in the url? Now when I think about it.
What happens when there is ;
in soap action? That would be quite broken.
if ctype and starts_with(ctype, soap_action_ctype) then | ||
-- The Content-Type can be a mix of upper and lower-case chars. Convert it to | ||
-- include only lower-case chars to be able to compare it. | ||
if ctype and starts_with(lower(ctype), soap_action_ctype) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would not work for application/xml+soap ; action=""
because of the whitespace.
…value Accepted formats defined here: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
spec/policy/soap/policy_spec.lua
Outdated
local policy_config = { | ||
mapping_rules = { | ||
{ | ||
pattern = '/soap_action$', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the RFC says these should be full URL we should test also with full URLs. To verify all the escaping works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 excellent 🥇
This policy adds support for a very small subset of SOAP.
This policy basically expects a SOAPAction URI in the SOAPAction header or the content-type header.
The SOAPAction header is used in v1.1: https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383528, whereas the Content-Type header is used in v1.2: https://www.w3.org/TR/soap12-part2/#ActionFeature
The SOAPAction URI is matched against the mapping rules defined in the configuration of the policy and the usage resulting from that is authorized and reported against 3scale's backend.