This repository implements a Zero Trust sidecar pattern that replaces static, long-lived API keys with short-lived, identity-based OIDC tokens. By verifying the "Workload Identity" of a GitHub Action runner, your infrastructure can enforce granular access control based on the specific repository, organization, or environment making the request.
📖 Full walkthrough on: How to Secure APIs Using OIDC and GitHub Actions (No Secrets) -- eparon.me
This project moves authentication and authorization out of the application and into the infrastructure.
- Workload Identity: Instead of a secret (e.g. service-account, token), the GitHub Action runner requests a signed JWT from GitHub’s OIDC provider.
- Sidecar Proxy: An Envoy instance sits in front of the API (NGINX), intercepting all traffic.
- Identity Verification: Envoy validates the token's signature and uses a custom (Lua) filter to authorize access based on claims like
repositoryorworkflow.
Static secrets in CI/CD are a common attack vector. This approach:
- Eliminates stored credentials
- Uses short-lived, scoped tokens
- Enables Zero Trust patterns in pipelines
See more on how to get started locally
kubectl apply -f kubernetes/envoy-config.yaml -n oidc-experiments
kubectl apply -f kubernetes/deployment.yaml -n oidc-experiments
kubectl apply -f kubernetes/service.yaml -n oidc-experimentskubernetes/– Kubernetes manifests for deploying Envoy + NGINXlocal-lab/– Docker-based environment to simulate OIDC flows locallytemplates/– Example GitHub Actions workflows
The kubernetes folder contains pure Kubernetes manifests. This approach ensures total transparency in how the proxy and application are configured.
Security Hardening:
- Localhost Isolation: The application container (NGINX) listens only on
127.0.0.1, ensuring no traffic can bypass the Envoy security layer. - Automated Verification: Envoy automatically fetches GitHub's public keys (JWKS) to verify token authenticity.
- Least Privilege: The sidecar only allows requests that match a specific GitHub Organization or Repository.
Testing OIDC logic typically requires pushing code to GitHub and waiting for a runner. The Local Lab breaks this cycle by providing a simulating environment for the entire OIDC lifecycle.
Features:
- Mock IDP: Uses
navikt/mock-oauth2-serverto act as GitHub's Identity Provider. - Fast Iteration: Debug your claim validation logic (e.g. via Lua filters) and Envoy configurations in seconds.
- Security Testing: Includes a configuration to simulate "Malicious" requests from unauthorized repositories to verify that your
401 - Unauthorized/403 - Forbiddenlogic is working as expected.
See more about how to run the lab in the corresponding README.
While the OIDC token proves the request is authentic, the Lua filter handles the authorization. It inspects the claims within the JWT to ensure the caller has the right to access this specific resource:
-- Example: Restrict access to a specific repository
local claims = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")["jwt_payload"]
local repository = claims["repository"]
if repository ~= "your-org/your-repo" then
request_handle:logInfo("Unauthorized repository access attempt: " .. tostring(repository))
request_handle:respond({[":status"] = "403"}, "Access Denied: Repository not authorized\n")
endThis repository is the companion code for the article: How to Secure APIs Using OIDC and GitHub Actions (No Secrets) -- eparon.me
Explore the blog post for a comprehensive guide on:
- The transition from Static Secrets to Workload Identity.
- Configuring GitHub Actions permissions for OIDC.
- The internal mechanics of the Envoy jwt_authn filter.
© 2026 Nontas Rontogiannis. Licensed under the MIT License.
