This service serves a 1×1 transparent image at any path ending with .png or .gif. Each request is treated as a heartbeat from the client's IP address, adding an IP Rule in Pangolin for some of your resources. Optionally, it can also allowlist your IP with your crowdsec instance. A background task cleans up the rules if an IP hasn't been seen for a configurable period.
Tested with Pangolin v1.10.3.
- Clone this repository and change into the directory
git clone https://github.com/tobkim/pangolin-ip-rule-manager.git
cd pangolin-ip-rule-manager
cp config.env.sample config.env- Configure the values in config.env:
- PANGOLIN_URL: Your Pangolin API base URL
- ORG_ID: Your Pangolin organization ID (string)
- PANGOLIN_TOKEN: API token with the required permissions (see below)
- RESOURCE_IDS: Comma-separated list of resource IDs to manage (e.g., 2,7,12). The available resource ids cannot be seen anymore in the newer pangolin versions. They are listed in the logs at the start of the container to help you out.
- Crowdsec Integration: Check the config.env.sample
- Start the service
docker compose up -d-
configure a new resource in Pangolin and point it to this container, e.g. a subdomain checkin.yourdomain.com
-
access https://checkin.yourdomain.com/checkin.png (or /whatever_as_long_as_its_png_or.gif). Your IP should now be in the list of allowed IPs.
PANGOLIN_URL: Base URL of Pangolin API (default:https://api.url.of.your.pangolin.instance)PANGOLIN_TOKEN: Bearer token for Pangolin API (required for API actions)ORG_ID: Pangolin organization identifier used to list resources at startup (default:your_org_id)RESOURCE_IDS: Comma-separated resource IDs (example:2,7,12)EXPECTED_PANGOLIN_CUSTOM_HEADER_KEY: Required. Incoming requests must include this header with the exact value below.EXPECTED_PANGOLIN_CUSTOM_HEADER_VALUE: Required. Configure the same header in Pangolin on the resource fronting this service.-RETENTION_MINUTES: Minutes without seeing an IP before cleanup deletes rules (default:1440= 1 day)CLEANUP_INTERVAL_MINUTES: Cleanup frequency in minutes (default:60= 1 hour)
CROWDSEC_ENABLED: Set totrueto enable CrowdSec integration (default:false).- see config.env.sample for other default values
Behavior when enabled:
- On startup, ensure the named allowlist exists (create if needed).
- On each successful
/whatever.pngrequest, also add the IP to the allowlists. - When an IP expires per
RETENTION_MINUTES, remove it from the allowlists during cleanup.
Notes:
- This uses
csclion the same Docker host. If CrowdSec runs in a container, ensure this service can rundocker exec crowdsec cscli ...(e.g., by mounting the Docker socket or running on the host). - CrowdSec allowlist membership is cached to avoid running commands on every request. If an IP isn't in the cache, the service first verifies the current contents via
cscli allowlist <name> listbefore attempting add/remove. - Commands are attempted across common
cscliversions; warnings are logged if commands are unavailable.
- Extremely small and simple: Python stdlib only, no external dependencies
- Image endpoints: any path ending with .png or .gif returns a 1×1 transparent image (PNG or GIF). Requests to the root path '/' return 403.
- Security enforcement:
- Mandatory custom header (see EXPECTED_PANGOLIN_CUSTOM_HEADER_KEY/EXPECTED_PANGOLIN_CUSTOM_HEADER_VALUE) must be present on every request
- IP extraction from
X-Real-IP, thenX-Forwarded-For, then socket address - Pangolin API integration: GET current rules, PUT to add, DELETE to remove
- Persistent state: JSON file (default at
/data/state.json, persisted via a Docker volume in the provided compose file) - Background cleanup thread removes stale rules created by this service
- Optional CrowdSec integration: add/remove IPs from a named CrowdSec allowlist via
cscli(when enabled)
- On startup, the service fetches and prints the list of resources for
ORG_IDfrom Pangolin, showing name andresourceIdto help you chooseRESOURCE_IDS. Remote-Useris optional; if present it is logged for traceability.- If the required custom header (EXPECTED_PANGOLIN_CUSTOM_HEADER_KEY/EXPECTED_PANGOLIN_CUSTOM_HEADER_VALUE) is missing or has a different value, returns
403. - On each successful request, the real IP is determined and this service:
- Updates
last_seenfor that IP in the state file - Checks Pangolin rules for each
resourceIdand creates one if missing (rule-existence checks are cached per resource for about 1 hour, configurable) - Serves a tiny transparent image (PNG or GIF) depending on the requested file extension
- Updates
- A background task periodically deletes rules for IPs that this service created once they have not been seen for
RETENTION_MINUTESminutes.
- Only rules created by this service are deleted during cleanup. Existing rules discovered as already present are left intact.
- The state is persisted to a JSON file at
STATE_FILE(default/data/state.json). The provideddocker-compose.ymluses a named volume to persist this data across restarts. You can change or remove the volume mapping if you prefer ephemeral state.
Make sure the API token you configure has the following minimal permissions:
- Ability to list resources in the organization (read-only)
- Ability to list existing access rules for the specified resources
- Ability to create and delete IP-based access rules for those resources
The following screenshot shows the needed permissions to select:
You can trigger the invisible request to /banner.png from web apps (like Jellyfin) using a tiny CSS snippet. This keeps the page unchanged while causing the browser to fetch the image, which lets this service observe the requester IP and manage Pangolin rules.
Replace https://your-pangolin-ip-rule-manager-domain.com with your real domain pointing to this service.
body::after {
content: "";
position: fixed;
inset: -9999px; /* keep it far off-screen */
width: 1px;
height: 1px;
pointer-events: none;
background-image: url("https://your-pangolin-ip-rule-manager-domain.com/jellyfin-checkin.png");
background-repeat: no-repeat;
}- This project was created and is largely maintained with the help of AI assistants. While kept intentionally simple and reviewed for practicality and safety, it may contain mistakes or omissions.
- Always review the code, configuration, and security posture before deploying to production. Use at your own risk.
- Contributions, bug reports, and human review are highly encouraged.
