GCP url-map with cookie regex matching rule - cookies

I am setting up a GCP url map to route requests to backend services based on cookie values. Since cookies would have multiple key values, I am trying to use a regex matcher.
I need to route requests to backends based on region value from cookie.
A typical cookie would look like this: foo=bar;region=eu;variant=beta;
defaultService: https://www.googleapis.com/compute/v1/projects/<project_id>/global/backendServices/multi-region-1
kind: compute#urlMap
name: regex-url-map
hostRules:
- hosts:
- '*'
pathMatcher: path-matcher-1
pathMatchers:
- defaultService: https://www.googleapis.com/compute/v1/projects/<project_id>/global/backendServices/multi-region-1
name: path-matcher-1
routeRules:
- matchRules:
- prefixMatch: /
headerMatches:
- headerName: Cookie
regexMatch: (region=us)
priority: 0
service: https://www.googleapis.com/compute/v1/projects/<project_id>/global/backendServices/multi-region-1
- matchRules:
- prefixMatch: /
headerMatches:
- headerName: Cookie
regexMatch: (region=eu)
priority: 1
service: https://www.googleapis.com/compute/v1/projects/<project_id>/global/backendServices/multi-region-2
However, this url-map fails validation with this error:
$ gcloud compute url-maps validate --source regex-url-map.yaml
result:
loadErrors:
- HttpHeaderMatch has no predicates specified
loadSucceeded: false
testPassed: false
Please note that an exact match with cookie passes validation and matches correctly if cookie value is just something like this: region=us. The headerMatches section for exact match would look like this:
headerMatches:
- headerName: Cookie
exactMatch: region=us
Any pointers on what am I doing wrong here?
Thanks!

Your way of reasoning is correct but the feature you're trying to use is unsupported in external load balancing in GCP; it works only with internal load balancing.
Look at the last phrase from the documentation:
Note that regexMatch only applies to Loadbalancers that have their loadBalancingScheme set to INTERNAL_SELF_MANAGED.
I know it isn't the answer you're looking for but you can always file a new feature request on Google's IssueTracker and explain in detail what you want, how it could work etc.
You can always try to pass the region value in the http request - instead of requesting https://myhost.com all the time - also if you could add a suffix, for example: https://myhost.com/region1 it would allow the GCP load balancer rules to process it and direct the traffic to the backend you wish.
Have a look at this example what you can and can't do with forwarding rules in GCP. Another example here. And another one (mine) explaining how to use pathMatcher to direct traffic to different backend services.

Related

Istio fault injection

Istio supports fault injecting.
The example is using destination. Is there any way for me to use source in order to inject fault to downstream services?
I would suggest using sourceLabels with internal mesh gateway.
Example:
gateways:
- mesh
http:
- match:
- sourceLabels:
app: source-app-v1
read HTTPMatchRequest for more.
Unfortunately, there is no straightforward way to achieve what you looking for.
route.destination.host is a required field for HTTPFaultInjection, and it has to be unique [source], and unambiguously refer to a service in the service registry [source], so it cannot be wildcarded (with * for example).

AWS API Gateway - Parameter mapping path with HTTP API (overwrite:path)

I started looking into using AWS HTTP API as a single point of entry to some micro services running with ECS.
One micro service has the following route internally on the server:
/sessions/{session_id}/topics
I define exactly the same route in my HTTP API and use CloudMap and a VPC Link to reach my ECS cluster. So far so good, the requests can reach the servers. The path is however not the same when it arrives. As per AWS documentation [1] it will prepend the stage name so that the request looks the following when it arrives:
/{stage_name}/sessions/{session_id}/topics
So I started to look into Parameter mappings so that I can change the path for the integration, but I cannot get it to work.
For requestParameters I want overwrite the path like below, but for some reason the original path with the stage variable is still there. If I just define overwrite:path as $request.path.sessionId I get only the ID as the path or if I write whatever string I want it will arrive as I define it. But when I mix the $request.path.sessionId and the other parts of the string it does not seem to work.
How do I format this correctly?
paths:
/sessions/{sessionId}/topics:
post:
responses:
default:
description: "Default response for POST /sessions/{sessionId}/topics"
x-amazon-apigateway-integration:
requestParameters:
overwrite:path: "/sessions/$request.path.sessionId/topics"
payloadFormatVersion: "1.0"
connectionId: (removed)
type: "http_proxy"
httpMethod: "POST"
uri: (removed)
connectionType: "VPC_LINK"
timeoutInMillis: 30000
[1] https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-private.html
You can try to use parentheses. Formal notation instead of shorthand notation.
overwrite:path: "/sessions/${request.path.sessionId}/topics"
It worked well for me for complex mappings.
mapping template is a script expressed in Velocity Template Language (VTL)
dont remove the uri and the connectionId and it will work for you.
add only requestParameters:
overwrite:path: "/sessions/$request.path.sessionId/topics"

istio setting request size limits - lookup failed: 'request.size'

I am attempting to limit traffic by request size using istio. Given that the virtual service does not provide this I am trying to due it via a mixer policy.
I setup the following
---
apiVersion: "config.istio.io/v1alpha2"
kind: handler
metadata:
name: denylargerequest
spec:
compiledAdapter: denier
params:
status:
code: 9
message: Request Too Large
---
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
name: denylargerequest
spec:
compiledTemplate: checknothing
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: denylargerequest
spec:
match: destination.labels["app"] == "httpbin" && request.size > 100
actions:
- handler: denylargerequest
instances: [ denylargerequest ]
Requests are not denied and I see the following error from istio-mixer
2020-01-07T15:42:40.564240Z warn input set condition evaluation error: id='2', error='lookup failed: 'request.size''
If I remove the request.size portion of the match I get the expected behavior which is a 400 http status with a message about request size. Of course, I get it on every request which is not desired. But that, along with the above error makes it clear that the request.size attribute is the problem.
I do not see anywhere in istio's docs what attributes are available to the mixer rules.
I am running istio 1.3.0.
Any suggestions on the mixer rule? Or an alternative way to enforce request size limits via istio?
The rule match mentioned in the question:
match: destination.labels["app"] == "httpbin" && request.size > 100
will not work because of mismatched attribute types.
According to istio documentation:
Match is an attribute based predicate. When Mixer receives a request
it evaluates the match expression and executes all the associated
actions if the match evaluates to true.
A few example match:
an empty match evaluates to true
true, a boolean literal; a rule with this match will always be executed
match(destination.service.host, "ratings.*") selects any request targeting a service whose name starts with “ratings”
attr1 == "20" && attr2 == "30" logical AND, OR, and NOT are also available
This means that the request.size > 100 has integer values that are not supported.
However, it is possible to do with help of Common Expression Language (CEL).
You can enable CEL in istio by using the policy.istio.io/lang annotation (set it to CEL).
Then by using Type Values from the List of Standard Definitions we can use functions to parse values into different types.
Just as a suggesion for solution.
Alternative way would be to use envoyfilter filter like in this github issue.
According to another related github issue about Envoy's per connection buffer limit:
The current resolution is to use envoyfilter, reopen if you feel this is a must feature
Hope this helps.

Setting a custom call source header with Istio

I have a setup using Kubernetes and Istio where we run a set of services. Each of our services have an istio-sidecar and a REST-api. What we would like is that whenever a service within our setup calls another that the called service knows what service is the caller (Preferably through a header).
Looking at the example image from bookinfo:
bookinfo-image (Link due to <10 reputation)
This would mean that in the source code for the ratings service I would like to be able to, for example, read a header telling me the request came from e.g. Reviews-v2.
My intuition tells me that I should be able to handle this in the istio sidecars, but I fail to realise exactly how.
Until now I have looked at especially envoy filters in the hope that they could help me. I see that for the envoy filters I would be able to set a header, but what I don't see is how I would get the information about what service made the call in order to set it in the header.
Envoy automatically sets the X-Forwarded-Client-Cert header, which contains the SPIFFE ID of the caller. SPIFFE ID in Istio is a URI in the form spiffe://cluster.local/ns/<namespace>/sa/<service account>. Practically, it designates the Kubernetes Service Account of the caller. You may want to test it by using the Istio httpbin sample and sending a request to httpbin:8000/headers
I ended up finding another solution by using a "rule". If we made sure that policy enforcing is enabled and then added the rule:
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: header-rule
namespace: istio-system
spec:
actions: []
requestHeaderOperations:
- name: serviceid
values:
- source.labels["app"]
operation: REPLACE
We achieved what we were attempting to do.

How to enable user-level rate limiting in istio

I saw Istio site mention Rate Limiting support but I can only find global rate-limit example.
Is it possible to do so at user-level? For example, if my user logged in but sends more than 50 requests within a second then I'd like to block said user, etc. In a similar situation, if user doesn't logged in then that device cannot send more than 30 requests per seconds.
Yes it is possible to conditionally apply rate limits based on arbitrary
attributes using a match condition in the quota rule.
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
name: quota
namespace: istio-system
spec:
match: source.namespace != destination.namespace
actions:
- handler: handler.memquota
instances:
- requestcount.quota
The quota will only apply when the source namespace is not equal to the destination namespace. In your case you probablty want to set a match like this:
match:
request:
headers:
cookie:
regex: "^(.*?;)?(user=jason)(;.*)?$"
I made a PR to improve the rate-limiting docs you can find it here: https://github.com/istio/istio.github.io/pull/1109