Releases · starwalkn/aastro · GitHub
Skip to content

Releases: starwalkn/aastro

0.10.0

Choose a tag to compare

@starwalkn starwalkn released this 27 Aug 22:25
211a21e

Fixed

  • Retries never fired for an upstream without an explicit method: - retry_on_statuses/timeout retries were
    silently a no-op for the common case of relying on the flow's own method. shouldRetry judged idempotency against
    the raw (often empty) config value instead of the effective method used for the actual request. (The 0.9.0 entry
    below describing this as fixed was itself wrong: the switch cases changed, but the call site never did.)
  • The TE response header was never stripped despite being listed in the hop-by-hop set - the map key was the literal
    string "TE", but net/http always stores and iterates response headers in net/textproto canonical form, which
    for TE is Te (no hyphen to separate words, so only the first letter is capitalized). The lookup silently missed
    every time, leaking the header to the client. Every other entry in that set was already canonical.
  • policy.header_blacklist entries were matched by exact string, not canonicalized - header_blacklist: ["x-secret"]
    silently never blocked a response header that net/http (correctly) stores as X-Secret. Same root cause as the
    TE fix above: HTTP header names are case-insensitive, but the code was comparing them as case-sensitive strings.

Changed

  • Breaking: flow option passthrough renamed to streaming (config field, OpenAPI x-aastro extension, and
    aastroctl openapi import --mode). Existing configs and specs must rename passthrough: true to streaming: true
    before upgrading.
  • Breaking: a flow with exactly one upstream is no longer wrapped in the JSON response envelope. Its upstream's
    status, headers, and body are now forwarded to the client as-is - on success, and on a client error or redirect
    (2xx/3xx/4xx), including bodies that aren't valid JSON, which previously got swallowed into an
    UPSTREAM_CLIENT_ERROR/UPSTREAM_REDIRECT envelope. Only a genuine gateway-side failure (upstream unavailable,
    5xx, timeout, policy violation) still returns a gateway-authored error body - see the envelope removal entry
    below for its current shape, which by the end of this release also covers multi-upstream flows. Single-upstream
    flows are now handled by their own dispatch path, bypassing the scatter/aggregate machinery entirely - see
    Router.dispatch/Router.buildProxyResponse.
  • aggregation is no longer required for a single-upstream flow, since it's never read for one - only a flow with
    more than one upstream must declare it (config validation enforces this in place of the old struct-tag check).
  • aastroctl openapi import --mode envelope renamed to --mode proxy, matching the response shape above; scaffolded
    (single-upstream) flows no longer get a meaningless aggregation: {strategy: array} block.
  • Internal: the several independent switch statements over the upstream error classification (retry eligibility,
    circuit breaker signal, policy applicability, client error code) were consolidated into one table
    (kindTable in upstream_error.go), checked exhaustive at startup. Not user-facing, but worth knowing if you're
    extending upstream error handling: add the new kind's row there rather than hunting down every switch.
  • Breaking: the {"data": ..., "errors": ..., "meta": ...} envelope is gone entirely, including for
    multi-upstream (aggregating) flows - a client no longer unwraps a gateway-specific shape to get at the payload:
    • A full or partial (206, bestEffort) success returns the aggregated data itself as the body - exactly the
      merge/array/namespace result, nothing wrapping it. Which upstreams failed on a partial success moves to
      the X-Partial-Errors response header (one value per failure, e.g. UPSTREAM_UNAVAILABLE) instead of an
      errors body field.
    • A response with no data at all - every upstream failed, or the request was rejected before reaching one
      (rate limit, payload too large, plugin failure, single-upstream gateway failure) - is now an
      RFC 9457 Problem Details document (application/problem+json):
      {"type": "about:blank", "title", "status", "detail"?, "errors": [...]}. type is always the literal
      "about:blank" - RFC 9457's own placeholder for "no further-specific type" - never a real URI, deliberately:
      a dereferencable type link is reconnaissance, since it names the gateway software fronting the request and
      invites probing for what that implies about the backend. errors is the machine-readable discriminator instead
      (always present, one entry per distinct underlying ClientError - more than one only for a multi-upstream
      failure with several different causes); title is a human summary of the highest-priority one and may change
      wording over time. ClientResponse/ResponseMeta are removed from the Go API; WriteError and the
      OpenAPI-generated schemas both moved to ProblemDetails.
    • Request correlation is unaffected by this - it was already carried solely by the X-Request-ID response
      header, never duplicated into a body field.
  • Breaking / security fix: builtin/middlewares/auth's default realm - sent in the WWW-Authenticate header on
    every 401 the gateway itself returns - changed from "aastro" to "restricted". Scope note: this is about the
    gateway's own runtime response to a client. OpenAPI export output and anything that only affects tracing/metrics
    (e.g. gateway.service.name) are a separate concern, owned by whoever runs and publishes them - not touched here.

0.9.0

Choose a tag to compare

@starwalkn starwalkn released this 09 Aug 16:11

Added

  • policy.follow_redirects - per-upstream option controlling whether the gateway follows upstream redirects. Defaults
    to false: a 3xx is now propagated to the client instead of being chased. Set it to true to restore the previous
    behavior, keeping in mind that the redirect is followed with the upstream's own TLS configuration and that Go strips
    Authorization and Cookie on cross-host hops.
  • New client error codes UPSTREAM_CLIENT_ERROR and UPSTREAM_REDIRECT, distinguishing an upstreams answer to the
    client from an upstream failure.

Changed

  • Upstream responses are now classified by status class rather than the single >= 500 check. 5xx and transport
    failures remain gateway failures (502, upstream body dropped); 3xx and 4xx are treated as answers addressed to the
    client.
  • Single-upstream flows propagate the upstream status verbatim - a 404, 401, or 302 from the upstream now reaches the
    client as a 404, 401, or 302. Aggregating flows are unaffected: a status is only propagated when every failed upstream
    agreed on it, otherwise the response stays 502.
  • allowed_statuses is now enforced as a contract in both directions: a status on the list is always a success (its
    body flows into data), and a status outside a non-empty list is a policy violation returning 502
    UPSTREAM_MALFORMED, even for statuses that would otherwise be propagated.
  • Client errors and redirects no longer count as circuit breaker failures - a burst of 404s can no longer take a healthy
    upstream out of rotation.
  • Responses with statuses that cannot carry a body (204, 304) are no longer wrapped in the JSON envelope.
  • Upstream policy validation is skipped for transport-level failures, which previously produced misleading
    empty body not allowed and status 0 not in allowed list messages on timeouts.

Fixed

  • A single-upstream flow returned 200 with the upstream's error body when the upstream answered 404, 401, or any
    other non-2xx status outside allowed_statuses. Clients received a success for a request that failed.
  • The circuit breaker reset itself with the very request it rejected: a denied call was recorded as a success, so an
    open breaker closed after blocking a single request instead of staying open until reset_timeout.
  • Retries never fired for upstreams without an explicit method:. Retry eligibility was judged against the raw
    configured method rather than the effective one, so the empty default was never considered idempotent.
  • Empty upstream bodies (204, or 200 with no content) were reported as UPSTREAM_MALFORMED during aggregation, turning
    a successful fan-out into a 206 partial response.
  • Aggregation error responses now carry the retried status correctly; an aborted read no longer produces a
    redirect-shaped errors entry for legitimately empty 3xx bodies.

0.8.0

Choose a tag to compare

@starwalkn starwalkn released this 07 Aug 16:32

Added

  • aastroctl openapi import — generate a gateway configuration from an OpenAPI 3.x document. Documents produced by openapi export --extensions are reconstructed losslessly: flows, aggregation, upstreams, policy, and transport are restored, with default-valued fields elided for a minimal, human-readable result. Foreign documents are scaffolded as single-upstream flows, with hosts taken from --default-host or servers[]. Passthrough flows are detected from streamed / responses, and the rate limiter is inferred from 429 responses. Secrets are never restored: plugin/middleware configs and TLS certificate paths are reported as warnings for manual re-adding. The generated config is validated before output, so import never emits a config the gateway would reject.

0.7.0

Choose a tag to compare

@starwalkn starwalkn released this 18 Jul 07:47

Added

  • Support for the HTTP QUERY method.
  • aastroctl openapi export — generate an OpenAPI 3.1 (or 3.0) document from a gateway configuration. Response statuses are derived from the actual config: 206 only for best-effort multi-upstream flows, 409 only under on_conflict: error, 429 only when the rate limiter is enabled. Flows guarded by the builtin auth middleware get a bearerAuth security scheme and a 401 response. Passthrough flows are modeled as streamed */* responses. Optional --extensions flag embeds x-aastro snapshots for future config round-trip (middleware configs and secrets are never serialized). Deterministic output, git-friendly. The config is loaded through the same pipeline as the gateway, so the command doubles as a config validator.

Changed

  • Client IP is now extracted only from trusted sources: X-Forwarded-For and X-Real-IP are honored only when the request comes from a trusted_proxies peer, with right-to-left XFF parsing. Prevents rate limiter bypass and client IP spoofing via forged headers.

0.6.0

Choose a tag to compare

@starwalkn starwalkn released this 05 Jul 10:26
68d38e5

Added

  • Hot reload of TLS certificates. Aastro watches the directories of the configured cert_file,
    key_file, and ca_file paths and atomically swaps the in-memory material
    when they change. New TLS handshakes use the new certificate; in-flight
    connections are unaffected. No configuration change is required — rotation
    works on the existing cert paths.

    Directory-level watching handles both atomic file replacement on a host
    (write-to-temp-then-rename) and Kubernetes secret mounts, where the projected
    files are updated via symlink swap rather than in-place writes. Certificate
    rotation through cert-manager, Vault Agent, or SPIFFE/SPIRE sidecars is now
    hands-off.

    Reloads are validated before they are applied: if a new certificate or CA
    bundle on disk fails to parse, the error is logged and the previously loaded
    material stays live, so a malformed rotation cannot take the listener down.

0.5.1

Choose a tag to compare

@starwalkn starwalkn released this 19 Jun 09:04

Fixed

  • Retries: retry_on_statuses is now honored for 5xx responses. Previously any
    status ≥ 500 was coerced into an error and retried unconditionally, ignoring the
    configured status list. 5xx and non-5xx are now governed by the same rule.
  • Retries: non-retryable failures (internal, body_too_large, policy_violation,
    circuit_open, canceled, read_error) are no longer retried — they previously
    looped until max_retries was exhausted with no chance of a different outcome.
  • Upstream errors: response body and headers of 5xx responses are no longer
    discarded. They are now read and preserved on the upstream response, so error
    detail is available to logging and downstream handling.

Added

  • Retries: idempotency guard. Status-based retries now apply only to idempotent
    methods (GET, HEAD, OPTIONS, TRACE, PUT, DELETE). Non-idempotent requests (e.g. POST, PATCH)
    are no longer replayed on a retryable status, preventing duplicate side effects.
    Transport-failure retries are unaffected.

Changed

  • Retries: retry decision logic consolidated into a single explicit rule.
    Transport-level failures (timeout, connection) are always retryable;
    status-based retries are driven solely by retry_on_statuses; everything else
    is terminal.
  • Used goccy/go-json instead of stdlib encoding/json

0.5.0

Choose a tag to compare

@starwalkn starwalkn released this 26 May 17:23

Added

  • New command-line interface and ctl for stub plugins initialization. For more information about the CLI, see the documentation.

Changed

  • Project was renamed from Kono to Aastro

0.4.1

Choose a tag to compare

@starwalkn starwalkn released this 24 May 11:32
a7d600f

Changed

Changed final image to chainguard/wolfi-base

Fixed

Used UUID v7 instead of ULID for request id

0.4.0

Choose a tag to compare

@starwalkn starwalkn released this 16 May 08:07

⚠️ Breaking Changes

The configuration schema has been restructured to separate concerns by listener and
responsibility. Existing 0.3.x configs will fail to load with a clear validation error
pointing at the missing sections.

Admin endpoints moved to a top-level admin section. Previously server.admin_port,
server.admin_bind_addr, and server.pprof lived inside server. They are now grouped
under their own section, since admin is a separate listener with separate semantics
(never TLS-terminated, bound to localhost by default, distinct timeouts).

# Before (0.3.x)
gateway:
  server:
    port: 7805
    admin_port: 9090
    admin_bind_addr: 127.0.0.1
    pprof:
      enabled: true

# After (0.4.0)
gateway:
  server:
    port: 7805
  admin:
    port: 9090
    bind_addr: 127.0.0.1
    enable_pprof: true

Observability moved to a top-level observability section. metrics and tracing
were previously nested under server. They are not server concerns - metrics is either
scraped from the admin port (Prometheus exporter) or pushed to OTLP, and tracing is
always push-only.

# Before (0.3.x)
gateway:
  server:
    metrics: { ... }
    tracing: { ... }

# After (0.4.0)
gateway:
  observability:
    metrics: { ... }
    tracing: { ... }

pprof no longer has its own port. Previously pprof.port opened a separate listener.
pprof endpoints now live on the admin port under /debug/pprof/, controlled by
admin.enable_pprof. One fewer port to manage and to expose through network policy.

Flow field max_parallel_upstreams renamed to parallel_upstreams. Aligns docs
with actual behaviour and the code field name. Same semantics, same default
(2 × NumCPU).

Added

mTLS support, end-to-end. Both the data port (inbound mTLS) and individual upstreams
(outbound mTLS) can now be configured with client certificate authentication, custom CA
bundles, configurable minimum TLS version (1.2 or 1.3), and SNI override.

gateway:
  server:
    tls:
      enabled: true
      cert_file: /etc/kono/server.crt
      key_file:  /etc/kono/server.key
      client_auth: require        # none | optional | require
      client_ca_file: /etc/kono/client-ca.crt
      min_version: "1.2"

  routing:
    flows:
      - upstreams:
          - tls:
              enabled: true
              cert_file: /etc/kono/clients/users.crt
              key_file:  /etc/kono/clients/users.key
              ca_file:   /etc/kono/internal-ca.crt
              server_name: user-service.internal

Liveness and readiness probes. New /__ready endpoint on the admin port returns
200 while the gateway is accepting traffic and 503 once graceful shutdown begins.
This lets Kubernetes (or any orchestrator with readiness probes) remove the pod from
service endpoints before the data port stops accepting connections, enabling true
zero-downtime deploys. /__health continues to return 200 while the process is alive
and never checks dependencies.

Configurable admin timeout. New admin.timeout field (default 5m) controls
read/write timeout on the admin port. Replaces a previously hard-coded constant. The
generous default exists to accommodate long pprof captures (/debug/pprof/profile,
/debug/pprof/trace); production data-port timeouts remain short.

Configurable header timeouts. New server.header_timeout and admin.header_timeout
fields (default 5s each) set http.Server.ReadHeaderTimeout on the respective listeners.
Defends against Slowloris-style attacks where a client trickles request headers slowly
to exhaust the server.

Structured TLS handshake logging. http.Server.ErrorLog is now wired through the
gateway's zap logger on both data and admin listeners. TLS handshake failures (failed
client certificate verification, version mismatch, unsupported cipher) now appear in
the same structured log stream as the rest of the application instead of escaping to
stderr via the standard logger.

Changed

Admin listener binds to 127.0.0.1 by default. Previously bound to all interfaces.
The admin port carries diagnostic endpoints (/__health, /__ready, /metrics,
/debug/pprof/) that should not be exposed externally. Set admin.bind_addr: 0.0.0.0
explicitly if Prometheus scrapes from outside the pod network.

/metrics endpoint moved to the admin port. When metrics.exporter: prometheus,
the endpoint is now served on admin.port rather than the data port. This means
Prometheus can scrape Kono over plain HTTP without needing a client certificate, even
when the data port enforces mTLS.

Health probe response format changed. /__health now returns
{"status": "ok"} with Content-Type: application/json instead of plain text OK.
Consistent with the rest of the gateway's response format.

TLS 1.0 and 1.1 are not selectable. min_version accepts only "1.2" or "1.3".
RFC 8996 deprecated 1.0 and 1.1 in 2021, and they are disabled in modern clients
regardless of server configuration.

Migration Notes

For most users, the migration is mechanical: move admin_port, admin_bind_addr, and
pprof from server into a new top-level admin section, move metrics and tracing
from server into a new observability section. If you relied on metrics being on
the data port - point Prometheus at the admin port instead.

If you ran pprof on a separate port: that port can be closed, pprof now lives on the
admin port. Update any internal documentation or scrape configs.

0.3.1

Choose a tag to compare

@starwalkn starwalkn released this 11 May 07:38

Added

  • Add WWW-Authenticate header to auth middleware

Changed

  • Golang build version ldflags will now automatically come from the docker meta step outputs
  • Sliding window rate limiter instead of fixed-window

Fixed

  • Added missing masker plugin and cors middleware to image

Thanks to @na1tto for improving the auth middleware!