Aegis is an identity-aware HTTP egress proxy. The repository currently contains the first policy-aware slice on top of the MVP bootstrap: a runnable HTTP/CONNECT forward proxy, config loading, plain HTTP policy enforcement, runtime-wired Kubernetes and EC2 identity discovery, metrics, tests, CI, and deployment scaffolding.
Implemented in this bootstrap:
- YAML config loading and validation.
- HTTP proxying with policy enforcement for plain HTTP requests.
- Kubernetes and EC2 identity discovery wired into the running process.
- Multiple discovery providers evaluated in deterministic config order, with first-match precedence.
CONNECTtunneling with policy enforcement and TLS SNI validation for passthrough rules.- CA-backed TLS MITM for
CONNECTrules withtls.mode: mitm, including decrypted HTTP method/path enforcement. - Optional Proxy Protocol v2 support on the proxy listener so identity resolution can use the original client IP behind an L4 load balancer.
- Live
SIGHUPconfig reload for policy, DNS, discovery, and MITM CA changes. - Global
proxy.enforcement: auditmode for migration, with would-allow / would-deny metrics and logs while traffic keeps flowing. - Per-policy
enforcement: audit|enforceso rollout can stay selective even when the global mode remainsenforce. - Optional token-protected admin API on a separate localhost-only listener for immediate global audit/enforce override without reload.
- Optional localhost-only
pproflistener on a separate port for CPU, heap, goroutine, block, mutex, and trace profiling during perf and staging investigations. - Per-policy
bypass: trueshadowing so a matching policy can emit would-allow / would-deny signals without blocking traffic. - Configurable
proxy.unknownIdentityPolicy: allow|denyfor production hard-deny behavior when a source IP cannot be resolved to a known identity. - Admin tooling on a dedicated localhost-only listener for identity dump and
policy simulation, plus CLI subcommands for
validate,diff,dump-identities, andsimulate. - Optional per-identity concurrent connection limits across plain HTTP requests
and
CONNECTtunnels. - Configurable graceful shutdown with explicit CONNECT tunnel draining and force-close accounting when the grace period expires.
- Readiness semantics backed by discovery-provider freshness, with provider
status metrics and a separate
/readyzendpoint. - Additive remote policy discovery from cloud-native object storage
(
discovery.policies) for AWS S3, GCS, and Azure Blob, with background polling and deletion-aware snapshot reconciliation. - Structured JSON logging with
slog. - Prometheus metrics and
/healthz, including reload, Proxy Protocol, CONNECT, MITM certificate-cache and CA lifecycle counters, request decision counters, active tunnel and shutdown counters, policy-evaluation latency, upstream TLS error counters, and per-provider identity-map gauges. - Container build, GitHub Actions CI, Helm chart, and Fargate starter files.
Planned but not implemented yet:
- Remaining production hardening features.
Current runtime behavior:
- Plain HTTP policy enforcement uses the configured identity resolver before any upstream dial.
- Kubernetes discovery providers are started at boot in listed order, followed by listed EC2 discovery providers. The first provider that resolves a source IP wins.
- Remote policy discovery sources are additive to file-defined
policies. Aegis polls each configured object-store source, parses Kubernetes-styleProxyPolicyYAML documents from objects under the configured prefix, and reconciles the active remote snapshot per source so object and document deletions remove policies cleanly. - Provider startup failures are tolerated as long as at least one configured provider becomes active; failures are surfaced through structured logs and Prometheus metrics.
- Active discovery providers are tracked as
active,stale, ordown./healthzremains liveness-only, while/readyzfails if discovery is configured and no provider is currentlyactive. CONNECTrequests resolve identity, evaluate policy, require a TLS ClientHello with matching SNI, and then run in passthrough or MITM mode depending on the matched rule.- When
proxy.enforcement: auditis set, Aegis still evaluates policy and emits audit metrics/logs, but it does not block policy-denied traffic. To keep migration traffic transparent, audit-modeCONNECTstays in raw passthrough rather than active MITM inspection. - When a matching policy sets
enforcement: audit, that policy stays in shadow mode even while the global effective mode isenforce. - When
admin.enabled: trueis configured, Aegis starts a separate localhost-only admin listener atadmin.listen.POST /admin/enforcement?mode=auditforces global audit mode immediately,mode=enforceforces blocking immediately, andmode=configclears the override and returns to the configuredproxy.enforcementvalue.GET /admin/enforcementreports the configured, override, and effective modes,GET /admin/runtimereturns the active MITM CA issuer plus any loaded companion CAs,/admin/identitiesreturns the live discovery map,/admin/simulateevaluates a hypothetical request against the active runtime state, andaegis_enforcement_modeexposes the effective mode in Prometheus./metrics,/healthz, and/readyzstay on the metrics listener without authentication. - When a matching policy sets
bypass: true, that policy behaves like a scoped shadow rule: Aegis records would-allow / would-deny outcomes for the match but still forwards the traffic. As with global audit mode, bypassedCONNECTrequests stay in transparent passthrough rather than active MITM. - When
proxy.unknownIdentityPolicy: denyis set, requests from unresolved source IPs are denied in enforce mode and recorded as would-deny in audit mode before any upstream dial happens. - TLS MITM requires
proxy.ca.certFileandproxy.ca.keyFile; once configured, Aegis terminates client TLS, verifies upstream TLS, and evaluates decrypted HTTP requests before forwarding them.proxy.cais always the active issuing CA for forged MITM leaf certificates. During CA rotation windows,proxy.ca.additional[]keeps companion CAs loaded so the runtime can report the full CA set and distinguish issuer rotation from companion-only reload changes. - When
proxy.proxyProtocol.enabledis set, the proxy listener requires Proxy Protocol v2 on inbound connections and uses the forwarded source IP for request identity resolution. SIGHUPreloads the config file in place. Listener settings stay immutable during reload:proxy.listen,metrics.listen,admin.listen,admin.enabled, andproxy.proxyProtocol.*changes are rejected and require a process restart.- Shutdown uses
shutdown.gracePeriodto stop accepting new requests, drain active CONNECT tunnels, and force-close any remaining hijacked tunnels when the grace period expires. proxy.idleTimeoutbounds idle CONNECT passthrough and MITM sessions so stalled clients or upstreams do not hold hijacked sockets forever.- When
proxy.connectionLimits.maxConcurrentPerIdentityis greater than zero, Aegis limits each resolved identity across active HTTP requests and CONNECT tunnels combined. Limit hits return429 Too Many Requests.
Build and run:
make build
./bin/aegis -config aegis.example.yamlaegis.example.yaml keeps both discovery.kubernetes and discovery.ec2
empty so the quick start runs locally without requiring cluster credentials or
AWS credentials. To enable runtime Kubernetes discovery, add a provider entry
under discovery.kubernetes and configure its auth block with
provider: kubeconfig for local runs, provider: inCluster inside a cluster,
or the managed-cluster variants eks, gke, or aks with their
provider-specific fields. Policies now bind explicitly through
policies[].subjects, so each policy must reference one or more named
discovery providers through
subjects.kubernetes.discoveryNames or subjects.ec2.discoveryNames, or one
or more source CIDRs through subjects.cidrs. To enable EC2 discovery, add a
provider entry under discovery.ec2, set the
target AWS region, and define the tag filters that scope instance discovery.
To enable additive remote policy discovery, add one or more entries under
discovery.policies with provider: aws|gcp|azure, the bucket/container name,
and the recursive prefix to scan. Authentication defaults to the ambient
cloud credential chain for the selected provider, so the process should run
with AWS default credentials, GCP application default credentials, or Azure
default credentials as appropriate. Each discovered object may contain one or
more YAML documents separated by ---; each document must be a ProxyPolicy
resource such as:
apiVersion: aegis.io/v1alpha1
kind: ProxyPolicy
metadata:
name: remote-allow
spec:
subjects:
kubernetes:
discoveryNames: ["cluster-a"]
namespaces: ["default"]
matchLabels:
app: payments
egress:
- fqdn: "api.stripe.com"
ports: [443]
tls:
mode: passthroughRemote policies are merged additively with file-defined policies, and policy
names must remain unique across both sources. On poll failures, Aegis keeps the
last successfully applied remote snapshot for that source until a newer valid
snapshot replaces it.
To enable TLS MITM for CONNECT, provide a proxy CA certificate and key
through proxy.ca.certFile and proxy.ca.keyFile. To preserve client IPs
behind an NLB or similar L4 balancer, enable proxy.proxyProtocol.enabled
and configure the balancer to emit Proxy Protocol v2 on the proxy port; when
PPv2 is enabled, proxy.proxyProtocol.trustedCIDRs must restrict which direct
peers are allowed to supply forwarded source addresses. To reload policy or
discovery changes without restarting the process, send SIGHUP to Aegis.
When rotating trust, keep the new active issuer under proxy.ca and keep the
old CA loaded under proxy.ca.additional[], for example:
proxy:
proxyProtocol:
enabled: true
trustedCIDRs:
- "10.0.0.0/8"
ca:
certFile: /etc/aegis/ca/new-ca.crt
keyFile: /etc/aegis/ca/new-ca.key
additional:
- certFile: /etc/aegis/ca/old-ca.crt
keyFile: /etc/aegis/ca/old-ca.key
cache:
maxEntries: 10000A CIDR-only policy is also valid when you want to scope rules directly to source networks instead of discovery-backed identities:
policies:
- name: allow-office
subjects:
cidrs:
- "10.20.0.0/16"
egress:
- fqdn: "api.example.com"
ports: [443]
tls:
mode: passthroughFor migration, set proxy.enforcement: audit to shadow policy decisions
without blocking traffic. For staged rollout, keep the global mode enforced and
set policies[].enforcement: audit on the workloads that still need shadow
mode. For a narrower escape hatch, set bypass: true on a specific policy
instead. To enable the global kill switch and the admin tooling endpoints, set
admin.enabled: true, choose a loopback-only admin.listen, set
admin.token, and call the admin endpoint with Authorization: Bearer <token>.
By default, Aegis also blocks loopback, private, and link-local upstream
addresses after DNS resolution to reduce DNS rebinding and SSRF risk; use
dns.rebindingProtection.allowedHostPatterns or
dns.rebindingProtection.allowedCIDRs for explicit internal destinations.
Set proxy.unknownIdentityPolicy: deny when you want production default-deny
behavior for traffic whose source IP does not map to a known identity.
Set proxy.connectionLimits.maxConcurrentPerIdentity to cap concurrent
upstream usage per resolved identity during migration or steady-state rollout.
Use shutdown.gracePeriod to control how long Aegis drains in-flight traffic
before it force-closes remaining CONNECT tunnels during process shutdown.
To enable runtime profiling, set pprof.enabled: true and bind
pprof.listen to a localhost-only address on a separate port, for example:
pprof:
enabled: true
listen: "127.0.0.1:6060"Send traffic through the proxy:
curl -x http://127.0.0.1:3128 http://example.com
curl -x http://127.0.0.1:3128 https://example.comInspect metrics:
curl http://127.0.0.1:9090/healthz
curl http://127.0.0.1:9090/readyz
curl http://127.0.0.1:9090/metrics
curl -H 'Authorization: Bearer replace-me' \
'http://127.0.0.1:9091/admin/runtime'
curl -H 'Authorization: Bearer replace-me' \
'http://127.0.0.1:9091/admin/identities'
curl -H 'Authorization: Bearer replace-me' \
'http://127.0.0.1:9091/admin/simulate?sourceIP=10.0.0.10&fqdn=api.stripe.com&port=443&protocol=connect'
curl -H 'Authorization: Bearer replace-me' \
-X POST 'http://127.0.0.1:9091/admin/enforcement?mode=audit'
go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=15The repository includes a perf/ package for reproducible k6-based
performance baselines against local/subprocess, Kind/Helm, and Azure private
deployments. The Azure flow reads Terraform outputs from
deploy/azure/terraform, deploys the sample workload into AKS, and runs k6
inside the cluster so the measured path stays on the private ACI and VM
network. The standard sequence is:
eval "$(make azure-export-env)"eval "$AKS_GET_CREDENTIALS_COMMAND"make azure-deploy-workloadmake perf-azure-httpmake perf-azure-mitm
See perf/README.md for setup, scenario descriptions, and run commands.
Available commands:
make buildmake testmake e2emake e2e-kindmake lintmake fmtmake docker
OCI release image:
- pushes to
masterpublishghcr.io/moolen/aegisvia.github/workflows/release.yml - the release workflow publishes
linux/amd64andlinux/arm64 - tags include
master,latest, andsha-<commit>
CLI tooling:
./bin/aegis validate --config aegis.example.yaml./bin/aegis diff --current old.yaml --next new.yaml./bin/aegis dump-identities --admin http://127.0.0.1:9090 --token replace-me./bin/aegis simulate --admin http://127.0.0.1:9090 --token replace-me --source-ip 10.0.0.10 --fqdn api.stripe.com --port 443 --protocol connect
make e2e runs the lightweight tagged cross-process suite in e2e/.
It exercises the built aegis binary as a subprocess across reload-sensitive
runtime behavior and the main HTTPS protocol matrix: passthrough allow/deny,
no-SNI and SNI-mismatch blocking, MITM certificate generation, MITM inner HTTP
policy denial, client trust failure, upstream TLS validation, and audit-mode
plus per-policy audit and bypass allow-on-deny behavior for both HTTP and
CONNECT, unknown-identity deny handling, and concurrent-connection
enforcement for both protocols. The
heavier cluster-aware
suite from the original design doc is split out into
make e2e-kind, which creates a Kind cluster, loads a locally built Aegis
image, installs the shipped Helm chart, and verifies in-cluster proxy
allow/deny behavior plus Kubernetes-discovery-driven identity enforcement.
make e2e-kind requires Docker, kind, kubectl, and helm.
The local Go cache is not committed. If you need an isolated cache, use:
GOCACHE=$(pwd)/.cache/go-build GOMODCACHE=$(pwd)/.cache/mod go test ./...deploy/helm contains a minimal chart that renders the current bootstrap
service. deploy/fargate contains starter ECS/NLB files aligned with the
current runtime shape: proxy on port 3128, metrics on 9090, and config
mounted at /etc/aegis/aegis.yaml. deploy/azure contains Terraform-backed
cloud perf scaffolding plus repo-side helper scripts for exporting environment
variables, deploying a proxied AKS sample workload, and running the Azure HTTP
perf baseline.
These deployment files are scaffolding only. They reflect the current runtime:
plain HTTP requests are policy-enforced, while CONNECT now enforces policy
and validates SNI before entering passthrough or MITM mode. Kubernetes and EC2
discovery are runtime-wired today, multiple discovery providers are supported
in deterministic config order, and provider startup failures are tolerated when
at least one provider becomes active. For local development, discovery stays
disabled unless you configure a provider explicitly. The Helm chart includes an
optional proxyCA.existingSecret mount for the CA files referenced by
config.proxy.ca, and optional serviceAccount / rbac scaffolding so
in-cluster Kubernetes discovery can watch pods when you enable
config.discovery.kubernetes. Both config.proxy.enforcement: audit and
per-policy config.policies[].enforcement: audit or
config.policies[].bypass: true are supported for migration rollouts,
config.admin.enabled, config.admin.listen, and config.admin.token
control the localhost-only admin API, config.proxy.unknownIdentityPolicy controls default-deny
behavior for unresolved sources, config.proxy.ca stays the active issuing CA
for forged MITM leaf certificates, config.proxy.ca.additional[] keeps
companion CAs loaded during dual-CA rotation windows so GET /admin/runtime
can report the full CA set and reloads can distinguish rotated from
companions_changed, and
config.proxy.connectionLimits.maxConcurrentPerIdentity can be used as a
simple abuse-control guardrail. The Fargate scaffold also
exposes an
enable_proxy_protocol_v2 switch on the NLB target group so source IP
preservation can be paired with config.proxy.proxyProtocol.enabled.
aegis-design-doc.md: original product design draft.docs/superpowers/specs/2026-04-24-aegis-mvp-bootstrap-design.md: approved bootstrap design.docs/superpowers/plans/2026-04-24-aegis-mvp-bootstrap.md: implementation plan used for the bootstrap work.