README
¶
HTTPRoute Controller
Kubernetes controller that automatically generates Gateway API HTTPRoutes from Service annotations.
Overview
Convenience controller for creating HTTPRoutes. Watches Services with specific annotations and auto-generates HTTPRoute + ReferenceGrant resources, eliminating manual YAML creation for each exposed service.
Built with:
- Kubebuilder v4.5.1
- Gateway API v1.2.1
- Controller-runtime v0.20.2
Architecture
Controller Name: httproute.controller/service-controller
Reconciliation:
- Level-based triggers (reconciles full state)
- Idempotent operations
- OwnerReferences for same-namespace resources
- Finalizers for cross-namespace cleanup
Constraints:
- HTTPRoute must be in gateway namespace (Kubernetes blocks cross-namespace OwnerReferences)
- ReferenceGrant must be in service namespace
- Targets single gateway listener section (default:
https) - Single service per HTTPRoute (no aggregation)
Gateway API Compatibility:
- Works with any Gateway API compliant implementation
- Tested with: Envoy Gateway
- Should work with: Istio, Kong Gateway, Contour, NGINX Gateway Fabric, Traefik, and others
- Requires Gateway API CRDs v1.0+ (HTTPRoute v1, ReferenceGrant v1beta1)
Features
Annotation-driven automation:
- Watches Services with
httproute.controller/expose: "true"(configurable prefix) - Auto-generates HTTPRoute + ReferenceGrant from annotations
- No manual resource creation required
Cross-namespace security:
- HTTPRoute deployed in gateway namespace (e.g.,
envoy-gateway-system) - Service remains in application namespace (e.g.,
default) - ReferenceGrant enables secure cross-namespace backend references
- Prevents unauthorized Service access from other namespaces
- ReferenceGrant creation can be opted out via annotation (for manual management)
Observability:
- Kubernetes Events emitted for HTTPRoute/ReferenceGrant creation and deletion
- Events appear on the Service resource (
kubectl describe svc <name>) - Detailed logging for all ReferenceGrant operations (security-relevant)
Lifecycle management:
- Finalizers ensure HTTPRoute cleanup when Service is deleted
- OwnerReferences auto-delete ReferenceGrant with Service
- Removing
expose: "true"triggers resource cleanup - Idempotent reconciliation (safe to run multiple times)
Configuration:
- Gateway name/namespace configurable per Service via annotations
- Defaults:
main-gatewayingateway-system(configurable via CLI flags) - Port selection: explicit annotation or first Service port
- Listener section: configurable (default:
https)
Controller Lifecycle
sequenceDiagram
participant User
participant Service
participant Controller
participant GatewayNS as Gateway Namespace
participant ServiceNS as Service Namespace
User->>Service: Add annotations<br/>(expose=true, hostname)
Service->>Controller: Reconcile triggered
Controller->>Controller: Validate annotations
Controller->>GatewayNS: Create/Update HTTPRoute
Note over GatewayNS: HTTPRoute: myapp.example.com<br/>Backend: Service in app namespace
Controller->>ServiceNS: Create/Update ReferenceGrant
Note over ServiceNS: Allows HTTPRoute to reference Service
Controller->>Service: Add finalizer
Note over Service: Ensures cleanup on deletion
rect rgba(255, 100, 100, 0.1)
Note over User,ServiceNS: Service Deletion
User->>Service: Delete Service
Service->>Controller: Finalizer triggers cleanup
Controller->>GatewayNS: Delete HTTPRoute
Controller->>ServiceNS: Delete ReferenceGrant<br/>(via OwnerReference)
Controller->>Service: Remove finalizer
Service->>Service: Deletion completes
end
Usage
Annotations
All annotations use the fixed prefix httproute.controller.
| Annotation | Required | Default | Description |
|---|---|---|---|
httproute.controller/expose |
Yes | - | Set to "true" to enable |
httproute.controller/hostname |
Yes | - | DNS hostname (e.g., myapp.example.com) |
httproute.controller/gateway |
No | From controller flag | Gateway name override |
httproute.controller/gateway-namespace |
No | From controller flag | Gateway namespace override |
httproute.controller/section-name |
No | https |
Gateway listener section override |
httproute.controller/port |
No | First port | Service port |
httproute.controller/skip-reference-grant |
No | false |
Set to "true" to skip ReferenceGrant creation |
Example
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
annotations:
httproute.controller/expose: "true"
httproute.controller/hostname: "myapp.example.com"
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
Controller automatically creates:
-
HTTPRoute (in gateway namespace):
- Name:
default-myapp - Hostname:
myapp.example.com - Backend: Service
myappin namespacedefault - Cleanup via Service finalizer (cross-namespace OwnerRefs not supported)
- Name:
-
ReferenceGrant (in service namespace):
- Name:
myapp-backend - Allows HTTPRoute from gateway namespace to reference Service
- OwnerReference to Service (automatic garbage collection)
- Name:
Controller Configuration
The controller requires gateway configuration at startup:
| Flag | Helm Value | Required | Description |
|---|---|---|---|
--default-gateway |
controller.defaultGateway |
Yes | Default gateway name |
--default-gateway-namespace |
controller.defaultGatewayNamespace |
Yes | Default gateway namespace |
--default-section-name |
controller.defaultSectionName |
No (default: https) |
Gateway listener section |
Note: Annotation prefix is fixed to httproute.controller and not configurable.
Installation
Using Helm (Recommended)
From Helm repository:
helm repo add httproute-controller https://piotr1215.github.io/httproute-controller
helm repo update
helm install httproute-controller httproute-controller/httproute-controller \
--namespace httproute-system \
--create-namespace \
--set controller.defaultGateway="my-gateway" \
--set controller.defaultGatewayNamespace="envoy-gateway-system"
Required: You must set controller.defaultGateway and controller.defaultGatewayNamespace.
From GitHub release:
# Replace v0.3.5 with the desired version
helm install httproute-controller \
https://github.com/Piotr1215/httproute-controller/releases/download/v0.3.5/httproute-controller-0.3.5.tgz \
--namespace httproute-system \
--create-namespace
From source:
helm install httproute-controller ./helm/httproute-controller \
--namespace httproute-system \
--create-namespace
Using kubectl
kubectl apply -f https://raw.githubusercontent.com/Piotr1215/httproute-controller/main/dist/install.yaml
Development
Prerequisites
- go v1.23.0+
- kubectl v1.11.3+
- Access to a Kubernetes cluster
- Gateway API CRDs installed
Local Development
Install Gateway API CRDs:
make install
Run controller locally:
make run
Run tests:
make test
Build and test in cluster:
# Create KIND cluster
kind create cluster --name httproute-test
# Install Gateway API CRDs
kubectl apply -f config/crd/gateway-api/gateway-api-crds.yaml
# Build image
make docker-build IMG=httproute-controller:local
# Load image to KIND
kind load docker-image httproute-controller:local --name httproute-test
# Deploy controller
make deploy IMG=httproute-controller:local
# Run integration tests
make test-e2e
# Cleanup
kind delete cluster --name httproute-test
Uninstall:
make undeploy
make uninstall
Release
Releases are automated via GitHub Actions. To create a new release:
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0
The release pipeline automatically:
- Builds and pushes multi-platform Docker images to
piotrzan/httproute-controller - Packages the Helm chart with correct version metadata
- Generates
install.yamlbundle - Creates GitHub release with artifacts
- Updates Helm repository index on GitHub Pages
Release artifacts:
- Docker image:
piotrzan/httproute-controller:<version> - Helm chart: Available from GitHub releases and Helm repository
- Install bundle:
dist/install.yaml
Security
Image Verification
All container images are signed with Cosign using keyless signing (GitHub OIDC).
Verify image signature:
cosign verify \
--certificate-identity-regexp="https://github.com/Piotr1215/httproute-controller/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
piotrzan/httproute-controller:0.3.5
View SBOM:
docker scout sbom piotrzan/httproute-controller:0.3.5
View provenance attestation:
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity-regexp="https://github.com/Piotr1215/httproute-controller/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
piotrzan/httproute-controller:0.3.5 | jq .
Release Artifact Verification
GitHub release artifacts (Helm charts, install.yaml) are also signed with Cosign.
Verify Helm chart signature:
# Download release artifacts
VERSION=v0.3.5
wget https://github.com/Piotr1215/httproute-controller/releases/download/${VERSION}/httproute-controller-${VERSION#v}.tgz
wget https://github.com/Piotr1215/httproute-controller/releases/download/${VERSION}/helm-chart.sig
# Verify signature
cosign verify-blob \
--certificate-identity-regexp="https://github.com/Piotr1215/httproute-controller/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--signature helm-chart.sig \
httproute-controller-${VERSION#v}.tgz
Verify install.yaml signature:
# Download artifacts
wget https://github.com/Piotr1215/httproute-controller/releases/download/${VERSION}/install.yaml
wget https://github.com/Piotr1215/httproute-controller/releases/download/${VERSION}/install.yaml.sig
# Verify signature
cosign verify-blob \
--certificate-identity-regexp="https://github.com/Piotr1215/httproute-controller/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--signature install.yaml.sig \
install.yaml
License
MIT License - see LICENSE file for details.