Aastro API Gateway

August 20, 2026 · View on GitHub

A lightweight, modular, high-performance API Gateway for modern microservices - parallel fan-out, declarative response aggregation, and .so plugins, configured in YAML.

Go Version License GitHub release Docker Pulls Coverage Status

Documentation · Configuration reference · Changelog · Discussions

Status: 0.x. The gateway is used in real deployments and every release is documented, but the configuration schema is still allowed to change in minor versions. Breaking changes are always called out in the changelog with a migration note. Pin an exact image tag.


What it does

One HTTP request in, several upstream calls out in parallel, one response back - merged, namespaced, or arrayed, with per-upstream timeouts, retries, and circuit breakers. Everything is described in a YAML file; no code is required to add a route.

# aastro.yaml - a complete, minimal configuration
schema: v1

gateway:
  server:
    port: 7805
  admin:
    port: 9090
    bind_addr: 0.0.0.0        # 127.0.0.1 by default; open it up inside a container

  routing:
    flows:
      - path: /api/v1/customers/{customer_id}
        method: GET

        aggregation:
          strategy: merge     # merge | array | namespace
          best_effort: true   # answer with partial data instead of failing

        upstreams:
          - name: users
            hosts: https://users.internal
            path: /v1/users/{customer_id}
            forward_params: ["customer_id"]
            timeout: 3s

          - name: orders
            hosts: https://orders.internal
            path: /v1/customers/{customer_id}/orders
            forward_params: ["customer_id"]
            timeout: 2s
            policy:
              retry:
                max_retries: 2
                retry_on_statuses: [502, 503, 504]
                backoff_delay: 100ms
$ curl -s localhost:7805/api/v1/customers/42
{
  "id": "42",
  "email": "ada@example.com",
  "orders": [
    { 
      "id": "9001", 
      "total": 1200
    }
  ]
}

If orders is down and best_effort is on, the client gets 206 Partial Content with the X-Partial-Errors header:

HTTP/1.1 206 Partial Content
X-Request-ID: 018f4a2b-7c3d-7e4f-a5b6-c7d8e9f0a1b2
X-Request-Fingerprint: 3f9a1c2b7e4d5061
X-Partial-Errors: UPSTREAM_UNAVAILABLE
Content-Type: application/json; charset=utf-8

{
  "data": {
    "id": "42",
    "email": "ada@example.com"
  }
}

Every configuration option, with comments, lives in sample.config.yaml.


Quick start

Docker

docker run \
  -p 7805:7805 \
  -v "$(pwd)/aastro.yaml:/etc/aastro/config.yaml:ro" \
  starwalkn/aastro:latest

/etc/aastro/config.yaml is the default config path. To mount it elsewhere, point AASTRO_CONFIG at it or pass -c /path/to/config.yaml after the image name.

From source

Building the gateway requires CGO_ENABLED=1 and a C toolchain, because plugins are Go shared objects (-buildmode=plugin).

git clone https://github.com/starwalkn/aastro.git
cd aastro

make all GOOS=<YOUR_OS> GOARCH=<YOUR_ARCH>   # builds .bin/aastro, .bin/aastroctl and the builtin .so files

./.bin/aastro -c aastro.yaml

Validate before you deploy

aastro -t -c aastro.yaml    # parse + validate, exit non-zero on error
aastro -T -c aastro.yaml    # same, plus dump the effective config (defaults applied) to stdout

Features

Routing & aggregation

  • Parallel fan-out to any number of upstreams, bounded by parallel_upstreams
  • merge, array, and namespace aggregation strategies with configurable conflict policy
  • Best-effort mode: 206 Partial Content instead of an all-or-nothing failure
  • Streaming flows for SSE, chunked, and long-lived streaming responses
  • Path parameter extraction and forwarding; header and query allow-lists

Resilience

  • Retries with an idempotency guard - non-idempotent methods are never replayed
  • Circuit breaker per upstream, with state exported as a Prometheus metric
  • Load balancing across hosts: round_robin or least_conns
  • Per-IP sliding-window rate limiting with trusted-proxy-aware client IP resolution
  • Response size limits, status allow-lists, header blacklists

Security

  • TLS and mutual TLS on the inbound data port and per upstream
  • Zero-downtime certificate hot-reload - cert-manager, Vault Agent, and SPIFFE/SPIRE ready
  • Builtin JWT auth middleware
  • Admin port bound to localhost by default and never TLS-terminated

Observability

  • Prometheus exporter or OTLP push for metrics
  • Distributed tracing over OTLP with W3C Trace Context and Baggage propagation
  • X-Request-Fingerprint correlation across logs, metrics, and traces
  • /__health, /__ready, /metrics, and optional /debug/pprof/ on a separate admin port

Extensibility

  • Request- and response-phase plugins, plus per-flow middlewares, loaded as .so files
  • Builtin middlewares: auth, cors, compressor, logger, recoverer
  • Builtin plugins: camelify, snakeify, masker
  • Skeleton generator: aastroctl plugin init

Operations

  • Single YAML file, validated ahead of time by the same loader the gateway uses
  • OpenAPI 3.1/3.0 export and import via aastroctl
  • Multi-arch (amd64/arm64) distroless-style image on chainguard/wolfi-base, running as a non-root user

aastroctl: OpenAPI in both directions

The gateway config is the source of truth, and aastroctl turns it into a spec your clients can consume - or turns someone else's spec into a starting config.

# Generate an OpenAPI 3.1 document from the gateway configuration.
# Statuses are derived from the actual config: 206 only for best-effort fan-out,
# 429 only when the rate limiter is on, 401 only behind the auth middleware.
aastroctl openapi export -c aastro.yaml -o openapi.yaml

# Round-trippable export: embeds x-aastro snapshots (never secrets).
aastroctl openapi export -c aastro.yaml --extensions -o openapi.yaml

# Scaffold a gateway configuration from any OpenAPI 3.x document.
aastroctl openapi import -i openapi.yaml -o aastro.yaml --default-host https://backend.internal

Export output is deterministic and diff-friendly, so the generated spec can live in git and be checked in CI. Import validates its own output - it never emits a config the gateway would reject.


Zero-downtime TLS certificate rotation

Aastro reloads TLS material - on both the inbound data port and outbound upstream connections - without restarting the process, reloading the config, or dropping connections. It watches the certificate directories and atomically swaps the in-memory material when files change.

  • Hands-off with your cert manager. Directory-level watching covers both atomic replacement on a host (write-temp-then-rename) and Kubernetes secret mounts, where projected files rotate via symlink swap.
  • Safe by construction. New handshakes use the new certificate; in-flight connections finish on the old one. If a rotated certificate or CA bundle fails to parse, the previously loaded material stays live - a bad rotation cannot take the listener down.
  • No configuration required. Rotation works on your existing cert_file, key_file, and ca_file paths. There is no flag to enable.
gateway:
  server:
    tls:
      enabled: true
      cert_file: /etc/aastro/server.crt   # rotate this file → picked up automatically
      key_file: /etc/aastro/server.key
      client_auth: require
      client_ca_file: /etc/aastro/client-ca.crt

  routing:
    flows:
      - upstreams:
          - tls:
              enabled: true
              cert_file: /etc/aastro/clients/users.crt   # outbound mTLS, also hot-reloaded
              key_file: /etc/aastro/clients/users.key
              ca_file: /etc/aastro/internal-ca.crt

Plugins

A plugin is an ordinary Go package built with -buildmode=plugin that exports a NewPlugin (or NewMiddleware) factory:

aastroctl plugin init --type response --name tenant_masker --author you
CGO_ENABLED=1 go build -buildmode=plugin -trimpath -o /etc/aastro/plugins/tenant_masker.so ./tenant_masker
plugins:
  - name: tenant_masker
    source: file            # builtin | file
    path: /etc/aastro/plugins/    # directory; <name>.so is resolved inside it
    config:
      header: X-Tenant-Id

Plugins must be compiled with the exact Go version and dependency set used for the gateway binary - Go's plugin ABI is unforgiving. See the plugin guide and CONTRIBUTING.md.


Roadmap

Development is driven by demonstrated demand rather than a fixed feature list. Open a discussion or upvote an existing issue - that is genuinely how the next milestone gets picked.


Contributing

Bug reports, plugins, benchmarks, and documentation fixes are all welcome. Start with CONTRIBUTING.md.

License

Apache-2.0 - see LICENSE.


Made with ❤️ in Go