Configure `:path` header for envoy ratelimiting - devops

I'm trying to use envoy ratelimiting functionality and need to ratelimit based on the entire url in my request, e.g. https://myenvoy.com/path/to/smth
Here is a part of my envoy.yaml
routes:
- match: { prefix: "/" }
route:
cluster: backend
rate_limits:
- stage: 0
actions:
- {request_headers: {header_name: ":path", descriptor_key: "path"}}
When I run
curl -k https://myenvoy.com/path/to/smth
The above configuration creates descriptor value /path/to/smth whereas I would like to have descriptor of value https://myenvoy.com/path/to/smth
Is it possible to configure that with envoy?
Thank you
PS: I looked at these header values and tried to use some, but it didn't help
https://github.com/envoyproxy/envoy/blob/master/source/common/http/headers.h

From further investigation,
- {request_headers: {header_name: "host", descriptor_key: "host"}}
does the job

Related

FastAPI RedirectResponse gets {"message": "Forbidden"} when redirecting to a different route

Please bare with me for a question for which it's nearly impossible to create a reproducible example.
I have an API setup with FastAPI using Docker, Serverless and deployed on AWS API Gateway. All routes discussed are protected with an api-key that is passed into the header (x-api-key).
I'm trying to accomplish a simple redirect from one route to another using fastapi.responses.RedirectResponse. The redirect works perfectly fine locally (though, this is without api-key), and both routes work perfectly fine when deployed on AWS and connected to directly, but something is blocking the redirect from route one (abc/item) to route two (xyz/item) when I deploy to AWS. I'm not sure what could be the issue, because the logs in CloudWatch aren't giving me much to work with.
To illustrate my issue let's say we have route abc/item that looks like this:
#router.get("/abc/item")
async def get_item(item_id: int, request: Request, db: Session = Depends(get_db)):
if False:
redirect_url = f"/xyz/item?item_id={item_id}"
logging.info(f"Redirecting to {redirect_url}")
return RedirectResponse(redirect_url, headers=request.headers)
else:
execution = db.execute(text(items_query))
return convert_to_json(execution)
So, we check if some value is True/False and if it's False we redirect from abc/item to xyz/item using RedirectResponse(). We pass the redirect_url, which is just the xyz/item route including query parameters and we pass request.headers (as suggested here and here), because I figured we need to pass along the x-api-key to the new route. In the second route we again try a query in a different table (other_items) and return some value.
I have also tried passing status_code=status.HTTP_303_SEE_OTHER and status_code=status.HTTP_307_TEMPORARY_REDIRECT to RedirectResponse() as suggested by some tangentially related questions I found on StackOverflow and the FastAPI discussions, but that didn't help either.
#router.get("/xyz/item")
async def get_item(item_id: int, db: Session = Depends(get_db)):
execution = db.execute(text(other_items_query))
return convert_to_json(execution)
Like I said, when deployed I can successfully connect directly to both abc/item and get a return value if True and I can also connect to xyz/item directly and get a correct value from that, but when I pass a value to abc/item that is False (and thus it should redirect) I get {"message": "Forbidden"}.
In case it can be of any help, I try debugging this using a "curl" tool, and the headers I get returned give the following info:
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Wed, 27 Jul 2022 08:43:06 GMT
x-amzn-RequestId: XXXXXXXXXXXXXXXXXXXX
x-amzn-ErrorType: ForbiddenException
x-amz-apigw-id: XXXXXXXXXXXXXXXX
X-Cache: Error from cloudfront
Via: 1.1 XXXXXXXXXXXXXXXXXXXXXXXXX.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: XXXXX
X-Amz-Cf-Id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
So, this is hinting at a CloudFront error. Unfortunately I don't see anything slightly hinting at this API when I look into my CloudFront dashboard on AWS, there literally is nothing there (I do have permissions to view the contents though...)
The API logs in CloudWatch look like this:
2022-07-27T03:43:06.495-05:00 Redirecting to /xyz/item?item_id=1234...
2022-07-27T03:43:06.495-05:00 [INFO] 2022-07-27T08:43:06.495Z Redirecting to /xyz/item?item_id=1234...
2022-07-27T03:43:06.496-05:00 2022-07-27 08:43:06,496 INFO sqlalchemy.engine.Engine ROLLBACK
2022-07-27T03:43:06.496-05:00 [INFO] 2022-07-27T08:43:06.496Z ROLLBACK
2022-07-27T03:43:06.499-05:00 END RequestId: 6f449762-6a60189e4314
2022-07-27T03:43:06.499-05:00 REPORT RequestId: 6f449762-6a60189e4314 Duration: 85.62 ms Billed Duration: 86 ms Memory Size: 256 MB Max Memory Used: 204 MB
I have been wondering if my issue could be related to something I need to add to somewhere in my serverless.yml, perhaps in the functions: part. That currently looks like this for these two routes:
events:
- http:
path: abc/item
method: get
cors: true
private: true
request:
parameters:
querystrings:
item_id: true
- http:
path: xyz/item
method: get
cors: true
private: true
request:
parameters:
querystrings:
item_id: true
Finally, it's probably good to note that I have added custom middleware to FastAPI to handle the two different database connections I need for connecting to other_items and items tables, though I'm not sure how relevant this is, considering this functions fine when redirecting locally. For this I implemented the solution found here. This custom middleware is the reason for the redirect in the first place (we change connection URI based on route with that middleware), so I figured it's good to share this bit of info as well.
Thanks!
As noted here and here, it is mpossible to redirect to a page with custom headers set. A redirection in the HTTP protocol doesn't support adding any headers to the target location. It is basically just a header in itself and only allows for a URL (a redirect response though could also include body content, if needed—see this answer). When you add the authorization header to the RedirectResponse, you only send that header back to the client.
A suggested here, you could use the set-cookie HTTP response header:
The Set-Cookie HTTP response header is used to send a cookie from the
server to the user agent (client), so that the user agent can send it back to
the server later.
In FastAPI—documentation can be found here and here—this can be done as follows:
from fastapi import Request
from fastapi.responses import RedirectResponse
#app.get("/abc/item")
def get_item(request: Request):
redirect_url = request.url_for('your_endpoints_function_name') #e.g., 'get_item'
response = RedirectResponse(redirect_url)
response.set_cookie(key="fakesession", value="fake-cookie-session-value", httponly=True)
return response
Inside the other endpoint, where you are redirecting the user to, you can extract that cookie to authenticate the user. The cookie can be found in request.cookies—which should return, for example, {'fakesession': 'fake-cookie-session-value-MANUAL'}—and you retrieve it using request.cookies.get('fakesession').
On a different note, request.url_for() function accepts only path parameters, not query parameters (such as item_id in your /abc/item and /xyz/item endpoints). Thus, you can either create the URL in the way you already do, or use the CustomURLProcessor suggested here, here and here, which allows you to pass both path and query parameters.
If the redirection takes place from one domain to another (e.g., from abc.com to xyz.com), please have a look at this answer.

Use traefik RedirectRegex globally

I'm aware of the fact that the regex middleware can be used as a docker-compose label and be externally configured in a for example treafik dynamic file
What I want to achieve is the same behavior on a global scale, which means all request coming into traffic on example.de should be redirected to example.com.
example.de/start -> example.com/start
Is there a way I can achieve this redirection behavior without specifying the middleware in every single docker-compose file ?
docker-compose.yml
- traefik.http.routers.test.middlewares=test-de-redirect#file
traefik_dynamic.yml
http:
middlewares:
test-de-redirect:
redirectRegex:
regex: "https://(.*)?example.de(.*)"
replacement: "https://${1}example.com${2}"
RedirectRegex

how to send post requests to AWS Lambda function created with serverless

TLDR
I cant figure out how to format my post request for my AWS Lambda API created with serverless
Current Implementation
I have a Lambda function created using the serverless framework. I have two functions; a function which prints out all modules and their version, and another that does a prediction on data sent via a post request. It handles dependency storage by mounting an EFS volume, here's the serverless.yml
service: test3Predict
plugins:
- serverless-pseudo-parameters
custom:
efsAccessPoint: fsap-**********
LocalMountPath: /mnt/efs
subnetsId: subnet-**********
securityGroup: sg-**********
provider:
name: aws
runtime: python3.6
region: us-east-2
timeout: 20
package:
exclude:
- node_modules/**
- .vscode/**
- .serverless/**
- .pytest_cache/**
- __pychache__/**
functions:
ohPredict:
handler: handler.lambda_handler_OH
environment: # Service wide environment variables
MNT_DIR: ${self:custom.LocalMountPath}
vpc:
securityGroupIds:
- ${self:custom.securityGroup}
subnetIds:
- ${self:custom.subnetsId}
iamManagedPolicies:
- arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
events:
- http:
path: ohPredict
method: get
fileSystemConfig:
localMountPath: '${self:custom.LocalMountPath}'
arn: 'arn:aws:elasticfilesystem:${self:provider.region}:#{AWS::AccountId}:access-point/${self:custom.efsAccessPoint}'
test:
handler: handler.test
environment: # Service wide environment variables
MNT_DIR: ${self:custom.LocalMountPath}
vpc:
securityGroupIds:
- ${self:custom.securityGroup}
subnetIds:
- ${self:custom.subnetsId}
iamManagedPolicies:
- arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
events:
- http:
path: test
method: get
fileSystemConfig:
localMountPath: '${self:custom.LocalMountPath}'
arn: 'arn:aws:elasticfilesystem:${self:provider.region}:#{AWS::AccountId}:access-point/${self:custom.efsAccessPoint}'
It seems like the test is working; If I run sls deploy and enter the URL for my test function in a browser, I get the expected output.
Problem
The ohPredict function is not working as well. When I send it a post request with the requests python module,
url = 'https://****.execute-api.****.amazonaws.com/dev/ohPredict'
myobj = {"data": data}
x = requests.post(url, json=myobj)
res = eval(x.text)
print(res)
I get the following error:
{'message': 'Missing Authentication Token'}
I ran a test with a simple lambda function and a post request without using serverless, and that was all the code I needed. I figured I would try experimenting and supplying a few values, and I ended up with this:
url = 'https://****.execute-api.****.amazonaws.com/dev/ohPredict'
myobj = {"data": data}
x = requests.post(url, headers={'Authorization': 'FOO'}, json=myobj)
res = eval(x.text)
print(res)
which resulted in:
{'message': "Authorization header requires 'Credential' parameter. Authorization header requires 'Signature' parameter. Authorization header requires 'SignedHeaders' parameter. Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header. Authorization=FOO"}
I tried playing around with supplying other values, and didn't get any change in the output. How can I get my post request to be recieved by my function?
As mentioned by yvesonline, the problem was the method. This was fixed by simply replacing method: get to method: post

connexion serve swagger ui at another url

We are using connexion to serve the swagger ui. We are using the openapi 3.0.0 specification. Here is a small part of our swagger.yml:
openapi: 3.0.0
servers:
- url: /
paths:
/resource:
...
/resource2:
...
in this case the ui is served at /ui. We are however using nginx to redirect all requests to /resource into this container. We would like swagger-ui to be served at /some-subdir/ui instead of at /ui, in order to be able to redirect the requests to the right container.
trial 1
openapi: 3.0.0
servers:
- url: /app
paths:
/resource:
...
/resource2:
...
which works, except that the resources are now served at /app/resource etc, while the same resource might in the future be served by another app, so we don't want the app name to appear in the URL of the resources (while it might be acceptable just for the swagger-ui).
trial 2
I found that, when constructing the connexion app, I could specify the swagger_url option:
options = {
'swagger_url': '/app/ui'
}
connexion_app = connexion.App(__name__, specification_dir='./', options=options)
now the swagger-ui is served at /app/ui, but the ui is trying to serve /openapi.json which is not reachable since not under /app (or any other subdir).
Almost there, there is another (well hidden) option to change the path to the openapi.json, the combination with swagger_url works:
options = {
'swagger_url': '/app/ui',
'openapi_spec_path': '/app/openapi.json'
}
connexion_app = connexion.App(__name__, specification_dir='./', options=options)

Prometheus scrape /metric with custom header

I have an application that will be monitored by Prometheus,
but the application need the custom header key like :
x-auth-token: <customrandomtoken>
What should I do with prometheus.yml?
Prometheus itself does not have a way to define custom headers in order to reach an exporter. The idea of adding the feature was discussed in this GitHub issue. Tl;dr: if you need a custom header, inject it with a forward proxy (I posted an example in another answer).
The prometheus-blackbox-exporter tag suggest that the question is about the exporter that makes probes, which is a separate thing and it does have a way to set headers. Only, it does not scrape metrics, it makes them.
Blackbox exporter has it's own configuration file and it consists of modules. A module is a set of parameters, defining how to do the probe and what result to expect. Here is an example of a module that looks for 200-299 response code and uses X-Auth-Token header:
modules:
http_2xx_with_header:
prober: http
http:
headers:
X-Auth-Token: skdjfh98732hjf22exampletoken
More examples can be found here and the list of configuration options - here.
When you made the blackbox exporter to load the new configuration, you need to adjust Prometheus configuration as well:
scrape_configs:
- job_name: 'blackbox'
metrics_path: /probe
params:
module: [http_2xx_with_header] # <- Here goes the name of the new module
static_configs:
- targets:
- http://prometheus.io
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9115
Prometheus doesn't support specifying custom HTTP headers, which must be sent with each scrape request to scrape target :( However, it supports specifying Authorization header via authorization and basic_auth options at scrape_config section.
For example, the following config instructs Prometheus to send Authorization: My-Auth my-super-secret header with each scrape request to http://localhost:8428/metrics:
scrape_configs:
- job_name: foo
authorization:
type: "My-Auth"
credentials: "my-super-secret"
static_configs:
- targets: ["localhost:8428"]
P.S. If you still need sending custom http headers with each request to remote target, then take a look at vmagent - monitoring agent, which understands Prometheus scrape configs and can scrape Prometheus targets. This is a project I work on. It provides additional features on top of standard scrape_configs from Prometheus, including headers option - see these docs. The headers option allows specifying arbitrary number of additional http headers, which need to be sent to scrape target. For example, the following config instructs sending x-auth-token: <customrandomtoken> http header with each request to http://foobar:1234:
scrape_configs:
- job_name: foo
headers:
- "x-auth-token: <customrandomtoken>"
static_configs:
- targets: ["foobar:1234"]

Resources