API

Fastapi - Ratelimiting

Fastapi & Rate Limiting

Let’s test a simple Rate Limiting Function found on the Net …

main.py

Main App with Rate Limiting Function

# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from Token_bucket import TokenBucket

app = FastAPI()

class RateLimiterMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, bucket: TokenBucket):
        super().__init__(app)
        self.bucket = bucket

    async def dispatch(self, request: Request, call_next):
        if self.bucket.take_token():
            return await call_next(request)

        # Return a JSON response for rate limit exceeded
        return JSONResponse(status_code=429, content={"detail": "Rate limit exceeded"})

# Token bucket with a capacity of 3 and refill rate of 1 token per second
bucket = TokenBucket(capacity=3, refill_rate=3)

# Apply the rate limiter middleware
app.add_middleware(RateLimiterMiddleware, bucket=bucket)

@app.get("/")
async def read_root():
    return {"message": "Hello World"}

Token_bucket.py

# Token_bucket.py
import time

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.tokens = capacity
        self.last_refill = time.time()

    def add_tokens(self):
        now = time.time()
        if self.tokens < self.capacity:
            tokens_to_add = (now - self.last_refill) * self.refill_rate
            self.tokens = min (self.capacity, self.tokens + tokens_to_add)
        self.last_refill=now

    def take_token(self):
        self.add_tokens()
        if self.tokens >= 1:
            self.tokens -=1
            return True
        return False

Test Script

Let’s produce some requests …

Apibench

Benchmark API’s

i like to work with fastapi. “FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.”. i also know that scripting languages like python are ways slower than compiled languages like c, c++, rust, …

why not build a little “hello world” api, running it on localhost, and then do a benchmark …

Rust

let’s start with rust.

Code

src/main.rs

Fastapi Project Template

Project Template for FastAPI

gave a try with a FastAPI Template, https://github.com/rochacbruno/fastapi-project-template.git

Projectname: gugus1234

clone the repo

git clone https://github.com/rochacbruno/fastapi-project-template.git gugus1234
cd gugus1234

Switch Poetry

i’d like to have poetry as virtual env manager

make switch-to-poetry

Rename some Stuff

had to rename some string in pyproject and different files …

mv project_name gugug1234
gsed -i 's/a-flask-test/gugus1234/' pyproject.toml
gsed -i 's/project_name/gugus1234/' pyproject.toml
gsed -i 's/project_name/gugus1234/g' gugus1234/cli.py gugus1234/app.py gugus1234/config.py gugus1234/security.py

Run Poetry once

poetry shell
poetry lock
poetry install

Admin User

let’s create admin user

Kuma - API

i like kuma. simple, flexibel, selfhosted, and open source. one thing i missed is an API for adding / modifing hosted services.

now, i found a webapi for kuma and gave a try.

pre-condition

  • you have some Maschine with Docker
  • you have traefik running, which can terminate TLS, handle Loadbalancing

docker-compose.yml

version: '3.3'

networks:
  traefik:
    external: true

volumes:
  uptime-kuma:
  api-db:

services:
  kuma:
    container_name: uptime-kuma
    image: louislam/uptime-kuma:1.19.6
    restart: always
    volumes:
      - uptime-kuma:/app/data
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kuma.rule=Host(`kuma.your.domain`)"
      - "traefik.http.routers.kuma.tls=true"

  api:
    container_name: backend
    image: medaziz11/uptimekuma_restapi:latest
    restart: always
    volumes:
      - ./db:/db:rwx
    environment:
      - KUMA_SERVER=${KUMA_SERVER:-http://kuma:3001}
      - KUMA_USERNAME=xxxxxx
      - KUMA_PASSWORD=xxxxxx
      - ADMIN_PASSWORD=xxxxxx
      - SECRET_KEY=${SECRET_KEY:-xxxxxx}
    depends_on:
      - kuma
    networks:
      - traefik

Get Token

# API
token=$(http --form POST 127.0.0.1:8001/login/access-token 'username=xxxxxx' 'password=xxxxxx' |jq '.access_token')

List Monitors

$ http -A bearer -a $token 127.0.0.1:8001/monitors
HTTP/1.1 200 OK
content-length: 15
content-type: application/json
date: Mon, 17 Apr 2023 04:48:59 GMT
server: uvicorn

{
    "monitors": []
}

Add Service

$ http -A bearer -a $token 127.0.0.1:8001/monitors type=http name=compass url=https://www.compass-security.com


HTTP/1.1 200 OK
content-length: 43
content-type: application/json
date: Mon, 17 Apr 2023 05:07:02 GMT
server: uvicorn


{
    "monitorID": 5,
    "msg": "Added Successfully."
}

Check Monitoring

$ http -A bearer -a $token 127.0.0.1:8001/monitors |jq '.monitors |map({id, name, url, active, interval})'
[
  {
    "id": 1,
    "name": "https://www.stoege.net",
    "url": "https://www.stoege.net",
    "active": true,
    "interval": 60
  },
  ... snip ...
  {
    "id": 5,
    "name": "compass",
    "url": "https://www.compass-security.com",
    "active": true,
    "interval": 60
  }
]

that’s great !

PowerDNS on OpenBSD

Run PowerDNS on OpenBSD

I’m mostly happy with NSD as Authoritative Nameserver. But why not look over the fence and have a look at PowerDNS ? At least the API looks promising to me …

Install Package

doas pkg_add powerdns--

Create Folder, DB and set Permission

doas mkdir /var/db/pdns
doas sqlite3 /var/db/pdns/pdns.sql < /usr/local/share/doc/pdns/schema.sqlite3.sql
doas chown -R _powerdns:wheel /var/db/pdns/

Update Config File /etc/pdns/pdns.conf

# DB
gsqlite3-database=/var/db/pdns/pdns.sql
launch=gsqlite3
setuid=_powerdns

# Tuning & Protection
max-queue-length=5000
overload-queue-length=2500

# Webserver
webserver=yes
webserver-address=ip-of-your-nameserver
webserver-allow-from=127.0.0.1,::1,my-remote-ip-address

Enable and Start Service

doas rcctl enable pdns_server
doas rcctl restart pdns_server

Import Data from NSD

If you have an existing NSD Setup, you can easily import the zones into the sqlite db.