{"id":31618082,"url":"https://github.com/g0lab/g0efilter","last_synced_at":"2026-02-08T04:06:30.344Z","repository":{"id":315573046,"uuid":"1060048631","full_name":"g0lab/g0efilter","owner":"g0lab","description":"Container Egress Traffic Filter","archived":false,"fork":false,"pushed_at":"2026-01-10T10:33:48.000Z","size":1220,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-11T00:42:49.520Z","etag":null,"topics":["alpine","container","dashboard","docker","docker-compose","egress-filtering","go","network-filtering","network-namespace","traffic-filtering"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/g0lab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-19T10:03:28.000Z","updated_at":"2026-01-10T10:32:44.000Z","dependencies_parsed_at":"2025-09-19T12:23:21.916Z","dependency_job_id":"a600c05d-8a3a-4b78-a7ae-67cb706b16f7","html_url":"https://github.com/g0lab/g0efilter","commit_stats":null,"previous_names":["g0lab/g0efilter"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/g0lab/g0efilter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g0lab%2Fg0efilter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g0lab%2Fg0efilter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g0lab%2Fg0efilter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g0lab%2Fg0efilter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/g0lab","download_url":"https://codeload.github.com/g0lab/g0efilter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g0lab%2Fg0efilter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28340393,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alpine","container","dashboard","docker","docker-compose","egress-filtering","go","network-filtering","network-namespace","traffic-filtering"],"created_at":"2025-10-06T13:45:25.926Z","updated_at":"2026-02-08T04:06:30.335Z","avatar_url":"https://github.com/g0lab.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![docker pulls](https://img.shields.io/docker/pulls/g0lab/g0efilter.svg?label=docker%20pulls)](https://hub.docker.com/r/g0lab/g0efilter)\n[![g0efilter CI](https://github.com/g0lab/g0efilter/actions/workflows/ci.yaml/badge.svg)](https://github.com/g0lab/g0efilter/actions/workflows/ci.yaml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fg0lab%2Fg0efilter.svg?type=shield\u0026issueType=security)](https://app.fossa.com/projects/git%2Bgithub.com%2Fg0lab%2Fg0efilter?ref=badge_shield\u0026issueType=security)\n[![Go Report Card](https://goreportcard.com/badge/g0lab/g0efilter)](https://goreportcard.com/report/g0lab/g0efilter)\n[![codecov](https://codecov.io/gh/g0lab/g0efilter/graph/badge.svg?token=owO27TfE79)](https://codecov.io/gh/g0lab/g0efilter)\n[![License](https://img.shields.io/github/license/g0lab/g0efilter.svg)](https://github.com/g0lab/g0efilter/blob/main/LICENSE)\n\n\u003e [!WARNING]\n\u003e g0efilter is in active development and its configuration may change often.\n\ng0efilter is a lightweight container designed to filter outbound (egress) traffic from attached container workloads. Run g0efilter alongside your workloads and attach them to share its network namespace to enforce a simple IP and domain allowlist policy.\n\n### Background\n\nAs a self-hoster running many open source apps, I wanted an easy way to restrict outbound connections from containers, as not all can be trusted. While Docker supports internal-only networks, some containers do need selective network and internet access. I wanted to support wildcard subdomains (e.g. `*.example.com`) without terminating TLS connections or relying on IP allowlisting alone, as CDNs can have multiple changing IPs and many subdomains. This could probably be achieved with third-party firewall products, but I wanted an open source, lightweight filter that runs alongside Docker Compose workloads...so here we are.\n\n### Features\n\n- **Egress filtering** - Explicitly allow specified IPs/CIDRs and domains in a policy file; anything not on the allowlist is blocked\n- **Wildcard subdomain support** - Allow wildcard subdomains like `*.example.com` to match any subdomain level\n- **Two filtering modes** - HTTPS (TLS SNI/HTTP Host inspection) or DNS-based filtering\n- **Live policy reloading** - Update policy.yaml without restarting containers\n- **Real-time dashboard** - Web UI with SSE streaming for traffic monitoring\n- **Remote unblock** - Unblock domains/IPs from the dashboard UI (with auth middleware)\n- **Notifications** - Gotify alerts for blocked traffic events\n\n### Quick Start\n\nRefer to the [examples](https://github.com/g0lab/g0efilter/tree/main/examples).\n\n### How it works\n\nAttach containers to g0efilter by setting `network_mode: \"service:g0efilter\"` in Docker Compose, which shares g0efilter's network namespace (network stack) with those containers. Traffic from attached containers is filtered based on a policy file that defines allowlisted IPs/CIDRs and domains.\n\n**Traffic to allowlisted IPs/CIDRs** bypasses all filtering and passes through directly.\n\n**Traffic to other IPs** is subject to domain-level filtering based on the `FILTER_MODE` environment variable (`https` or `dns`):\n\n- **HTTPS mode (default):** Outbound HTTP/HTTPS traffic on ports 80/443 is intercepted and redirected to local services running inside g0efilter. These services inspect plain text packet data, including the HTTP `Host` header (for HTTP) or TLS SNI from the TLS Client Hello (for HTTPS, without terminating the connection), and cross-reference it against the allowlist. If the domain is allowed, the connection is established and traffic flows through (the service acts as a middleman). If not found in the allowlist, the connection is reset and traffic is blocked.\n\n- **DNS mode:** DNS queries are intercepted and redirected to an internal DNS server. The server only resolves allowlisted domains and non-allowlisted queries receive NXDOMAIN responses. Note that direct IP connections bypass DNS filtering entirely.\n\n**Filter Logic Flow (HTTPS mode):**\n```\nStart\n│\n├─► Is destination IP in allowlist? ── Yes ─► ALLOW (skip remaining steps, no redirect)\n│                                   └─ No ─► continue\n│ \n├─► Is connection already established? ── Yes ─► ALLOW (skip remaining steps, no redirect)\n│                                      └─ No ─► continue\n│ \n├─► Is destination port 80 or 443? ── No ─► BLOCK\n│                                  └─ Yes ─► continue\n│\n├─► Redirect to local filter service (port 8080 for HTTP, 8443 for HTTPS)\n│\n├─► Extract domain from Host header (HTTP) or SNI (TLS)\n│\n├─► Does domain match allowlist? ── Yes ─► FORWARD to original destination\n│                                └─ No ─► DROP\n│\n└─► LOG decision → Send to dashboard (if enabled) ─► End\n```\n\n\u003e [!NOTE]\n\u003e Attached containers share g0efilter's network namespace and must not bind to ports used by g0efilter.  \n\u003e By default, g0efilter uses `HTTP_PORT` (8080), `HTTPS_PORT` (8443), and optionally `DNS_PORT` (53).\n\n### Dashboard container\n\nThe optional **g0efilter-dashboard** container runs a web UI on **port 8081** (by default). If `DASHBOARD_HOST` and `DASHBOARD_API_KEY` are set, g0efilter will ship logs to the dashboard.\n\nExample Dashboard Screenshot:\n\n![g0efilter-dashboard-example](https://raw.githubusercontent.com/g0lab/g0efilter/main/examples/images/g0efilter-dashboard-example.png)\n\n### Example policy.yaml\n\n```yaml\nallowlist:\n  ips:\n    - \"1.1.1.1\"\n    - \"192.168.0.0/16\"\n    - \"10.1.1.1\"\n  domains:\n    - \"github.com\"\n    - \"*.alpinelinux.org\"\n```\n\n\u003e [!NOTE]\n\u003e - The policy file supports live reloading: edits to policy.yaml automatically trigger rule and service updates without needing to restart the container. The internal g0efilter services are restarted, but the container itself remains running.\n\u003e - If you do not need live reloading, you can use environment variables (ALLOWLIST_IPS, ALLOWLIST_DOMAINS) instead of a policy file. If both are present, environment variables take precedence.\n\n### Environment variables\n\n### g0efilter\n\n| Variable            | Description                                        | Default             |\n| ------------------- | -------------------------------------------------- | ------------------- |\n| `LOG_LEVEL`         | Log level (TRACE, DEBUG, INFO, WARN, ERROR)        | `INFO`              |\n| `HOSTNAME`          | To identify which endpoint is sending the logs     | unset               |\n| `HTTP_PORT`         | Local HTTP port                                    | `8080`              |\n| `HTTPS_PORT`        | Local HTTPS port                                   | `8443`              |\n| `POLICY_PATH`       | Path to policy file inside container               | `/app/policy.yaml`  |\n| `ALLOWLIST_IPS`     | Comma-separated list of allowed IPs/CIDRs (takes precedence over policy file) | unset               |\n| `ALLOWLIST_DOMAINS` | Comma-separated list of allowed domains (takes precedence over policy file, supports wildcards like `*.example.com`) | unset               |\n| `FILTER_MODE`       | `https` (TLS SNI/HTTP Host) or `dns` (DNS name filtering)      | `https`             |\n| `DNS_PORT`          | DNS listen port                                    | `53`                |\n| `DNS_UPSTREAMS`     | Upstream DNS servers (comma-separated). Uses Docker's default DNS if not specified | `127.0.0.11:53`     |\n| `DASHBOARD_HOST`    | Dashboard URL for log shipping                     | unset               |\n| `DASHBOARD_API_KEY` | API key for dashboard authentication               | unset               |\n| `DASHBOARD_QUEUE_SIZE` | Queue size for buffering logs before sending to dashboard. Logs are dropped if queue is full | `1024` |\n| `DASHBOARD_START_DELAY` | Delay before starting dashboard log shipping (supports duration formats like `5s`, `1m`) | `5s` |\n| `LOG_FILE`          | Optional path for persistent log file              | unset               |\n| `NFLOG_BUFSIZE`     | Netfilter log buffer size                          | `96`                |\n| `NFLOG_QTHRESH`     | Netfilter log queue threshold                      | `50`                |\n| `NOTIFICATION_HOST`            | Gotify server URL for security alert notifications | unset               |\n| `NOTIFICATION_KEY`             | Gotify application key for authentication          | unset               |\n| `NOTIFICATION_BACKOFF_SECONDS` | Rate limit backoff period for duplicate alerts (in seconds) | `60`                |\n| `NOTIFICATION_IGNORE_DOMAINS`  | Comma-separated list of domains to ignore for notifications (supports wildcards like `*.example.com`) | unset               |\n| `ENABLE_REMOTE_UNBLOCK` | Enable polling dashboard for remote unblock requests | `false` |\n| `UNBLOCK_POLL_INTERVAL` | How often to poll dashboard for unblock requests (supports duration formats like `10s`, `1m`) | `10s` |\n\n### g0efilter-dashboard\n\n| Variable        | Description                                                                                                       | Default |\n| --------------- | ----------------------------------------------------------------------------------------------------------------- | ------- |\n| `PORT`          | Address/port the dashboard listens on (HTTP UI + API). Can be just a port (`8081`) or address+port (`:8081`)     | `:8081` |\n| `API_KEY`       | API key used to authenticate incoming log data from the `g0efilter` container. Must match `DASHBOARD_API_KEY`    | unset   |\n| `LOG_LEVEL`     | Log level (TRACE, DEBUG, INFO, WARN, ERROR)                                                                       | `INFO`  |\n| `BUFFER_SIZE`   | In-memory buffer size for events. Controls how many events can be queued before dropping                          | `5000`  |\n| `READ_LIMIT`    | Maximum number of events returned per read/API request                                                            | `500`   |\n| `SSE_RETRY_MS`  | Server-Sent Events (SSE) client retry interval in milliseconds                                                    | `2000`  |\n| `WRITE_TIMEOUT` | HTTP write timeout in seconds (0 = no timeout, recommended for SSE)                                               | `0`     |\n| `RATE_RPS`      | Maximum average requests per second (rate-limit)                                                                  | `50`    |\n| `RATE_BURST`    | Maximum burst size for rate-limiting (in requests)                                                                | `100`   |\n\n## Remote Unblock Feature\n\nThe **remote unblock** feature allows administrators to unblock domains or IPs directly from the dashboard UI. When enabled, g0efilter instances poll the dashboard for pending unblock requests and automatically update their policy files.\n\n\u003e [!WARNING]\n\u003e This feature is **disabled by default** (`ENABLE_REMOTE_UNBLOCK=false`). Do not enable this in non-test environments without proper authentication middleware protecting the dashboard. The `POST /api/v1/unblocks` endpoint must be protected by reverse proxy authentication (e.g., Authelia, Authentik, PocketID) to prevent unauthorized users from modifying your allowlist.\n\n## Dashboard Reverse Proxy Suggestion\n\nI would recommend to place the **g0efilter-dashboard** behind a reverse proxy such as Traefik with the following controls:\n\n### API Endpoints\n\n| Endpoint | Auth | Description |\n|----------|------|-------------|\n| `GET /health` | None | Health check for monitoring/load balancers |\n| `POST /api/v1/logs` | API Key | Log ingestion from g0efilter containers |\n| `GET /api/v1/unblocks?hostname=X` | API Key | Poll pending unblock requests (used by g0efilter) |\n| `POST /api/v1/unblocks/ack` | API Key | Acknowledge processed unblock (used by g0efilter) |\n| `GET /` | Middleware | Dashboard web UI |\n| `GET /api/v1/logs` | Middleware | Read logs |\n| `GET /api/v1/events` | Middleware | Server-Sent Events stream |\n| `DELETE /api/v1/logs` | Middleware | Clear logs |\n| `POST /api/v1/unblocks` | Middleware | Create unblock request (from dashboard UI) |\n| `GET /api/v1/unblocks/status` | Middleware | Poll unblock status (from dashboard UI) |\n\n### Example Traefik Configuration\n\nIf using Traefik as a reverse proxy, here's an example of a working yaml based configuration using two routers to handle different authentication requirements:\n\n```yaml\nhttp:\n  routers:\n    g0efilter-ingest-router:\n      entryPoints:\n        - websecure\n      rule: \"Host(`g0efilter.example.com`) \u0026\u0026 ((PathPrefix(`/api/v1/logs`) \u0026\u0026 Method(`POST`)) || PathPrefix(`/health`) || (Path(`/api/v1/unblocks`) \u0026\u0026 Method(`GET`) \u0026\u0026 QueryRegexp(`hostname`, `^.+$`)) || (Path(`/api/v1/unblocks/ack`) \u0026\u0026 Method(`POST`)))\"\n      service: g0efilter-dash-service\n      middlewares:\n        - security-headers\n        - ratelimit\n      tls:\n        certResolver: letsencrypt\n        domains:\n          - main: \"example.com\"\n            sans:\n              - \"*.example.com\"\n\n    g0efilter-dash-router:\n      entryPoints:\n        - websecure\n      rule: \"Host(`g0efilter.example.com`)\"\n      service: g0efilter-dash-service\n      middlewares:\n        - security-headers\n        - ratelimit\n        - auth-oidc  # Your auth middleware\n      tls:\n        certResolver: letsencrypt\n        domains:\n          - main: \"example.com\"\n            sans:\n              - \"*.example.com\"\n\n  services:\n    g0efilter-dash-service:\n      loadBalancer:\n        servers:\n          - url: \"http://g0efilter-dashboard:8081\"\n```\n\n**How it works:**\n- `g0efilter-ingest-router`: Matches `POST /api/v1/logs`, `/health`, `GET /api/v1/unblocks?hostname=X`, and `POST /api/v1/unblocks/ack` - no SSO required (API key auth)\n- `g0efilter-dash-router`: Matches all other requests to the dashboard - requires SSO/OIDC authentication\n- The more specific ingest router rule takes precedence for API calls and health checks\n- All other traffic (UI, reads, etc.) goes through the dashboard router with SSO protection\n\n\n### Example docker-compose.yaml\n\n```yaml\nservices:\n  g0efilter:\n    image: docker.io/g0lab/g0efilter:latest\n    container_name: g0efilter\n    volumes:\n      - ./policy.yaml:/app/policy.yaml\n    cap_drop:\n      - ALL\n    cap_add:\n      - NET_ADMIN # Required for nftables modification\n    security_opt:\n      - no-new-privileges\n    # Host-exposed port for dashboard (dashboard runs in same netns)\n    ports:\n      - 8081:8081 # Dashboard port\n    read_only: true\n    restart: always\n    env_file:\n      - .env\n\n  g0efilter-dashboard:\n    image: docker.io/g0lab/g0efilter-dashboard:latest\n    container_name: g0efilter-dashboard\n    # optional - custom user\n    # user: 1000:1000\n    cap_drop:\n      - ALL\n    security_opt:\n      - no-new-privileges\n    read_only: true\n    env_file:\n      - .env.dashboard\n    network_mode: \"service:g0efilter\"\n    restart: always\n\n  example-container:\n    image: docker.io/alpine/curl:latest\n    command: sh -c \"sleep infinity\"\n    network_mode: \"service:g0efilter\"\n```\n\n### Verifying the Container Signatures\n\nThe g0efilter container images are signed with [Cosign](https://github.com/sigstore/cosign) using keyless signing:\n\n```bash\n# Verify g0efilter container\ncosign verify g0lab/g0efilter:latest \\\n  --certificate-identity-regexp=https://github.com/g0lab/g0efilter \\\n  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \\\n  -o text\n\n# Verify g0efilter-dashboard container\ncosign verify g0lab/g0efilter-dashboard:latest \\\n  --certificate-identity-regexp=https://github.com/g0lab/g0efilter \\\n  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \\\n  -o text\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg0lab%2Fg0efilter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fg0lab%2Fg0efilter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg0lab%2Fg0efilter/lists"}