- Python 78.5%
- Shell 21.1%
- Just 0.4%
| docs | ||
| scripts | ||
| templates | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| deploy-env.sh | ||
| deploy.env.example | ||
| install-host.sh | ||
| Justfile | ||
| README.md | ||
| update-host.sh | ||
telegram-access-gate
Temporary SSH and Telegram-approved sudo access for servers where an automation agent or operator should not keep a permanently privileged login open.
Design
- Each server stands alone.
- Prefer one Telegram bot per server.
- A pinned Telegram group message can list server commands such as
/example_server,/server_2, and/server_3. - The selected server bot shows the current SSH/SUDO status directly when a session is active.
/statuswithout a server argument is intentionally broadcast-like: every bot that sees it answers with its own current state./<server>uses the configured server command for that server only; other server bots ignore foreign slash commands.- Request-only SSH uses a second locked-down user that can only send an access request.
- The real working user defaults to
access-gated. - The working user receives the managed SSH key only for the approved time window.
- Privileged commands go through
hakalya-sudo, which asks the allowed Telegram operator for approval.
No global controller is required. A controller or pinned command list is only UI convenience.
Security model
- Telegram actions are accepted only from
TELEGRAM_ALLOWED_USER_ID. - Approval questions can be posted to a shared group via
TELEGRAM_APPROVAL_CHAT_ID, while callbacks still requireTELEGRAM_ALLOWED_USER_ID. - Every callback includes a server name and is ignored on mismatches.
- Bot tokens are root-readable only.
- The request-only user has a forced command and no general shell path.
- Sudo is not passwordless except for narrow root-owned wrappers.
- Approval buttons are single-use in effect: first decision wins.
- Revocation is fail-closed through a systemd timer.
Files
scripts/hakalya-access-bot.py- Telegram bot for SSH allow/status/adjust/revoke callbacks and sudo decisions.scripts/hakalya-approved-sudo.py- root sudo approval wrapper.scripts/hakalya-request-access-root.py- root helper for request-only SSH.scripts/hakalya-gated-allow- root script that opens the gated SSH window.scripts/hakalya-gated-revoke- root script that removes expired managed keys.scripts/hakalya-sudo- user-facing sudo wrapper.scripts/sudo-shadow-/usr/local/bin/sudoshim that routes the gated user throughhakalya-sudowhile preserving/usr/bin/sudofor other users.templates/local-ssh-wrapper.sh- local wrapper that requests access and retries SSH.install-host.sh- host installer for one server.update-host.sh- updater for an already installed server..env.example- example host configuration with semantic placeholders.
Minimal install shape
Generate two local keypairs per server:
ssh-keygen -t ed25519 -N '' -f ~/.ssh/<server>_approval_ed25519 -C "access-gate-<server>-approval-ttl"
ssh-keygen -t ed25519 -N '' -f ~/.ssh/<server>_request_ed25519 -C "access-gate-<server>-request-only"
Then install on the server as root:
sudo ./install-host.sh \
--server example-server \
--allowed-telegram-id 123456789 \
--approval-public-key ~/.ssh/example-server_approval_ed25519.pub \
--request-public-key ~/.ssh/example-server_request_ed25519.pub
After install, place the Telegram bot token on the server:
sudoedit /etc/hakalya-access-gate/bot.env
Use .env.example as the non-secret shape. Required:
TELEGRAM_ALLOWED_USER_ID=<telegram-user-id>
TELEGRAM_BOT_TOKEN=<token>
ACCESS_GATE_SERVER_NAME=<server>
ACCESS_GATE_COMMAND=<telegram-slash-command-without-leading-slash>
ACCESS_GATE_WORKING_USER=access-gated
ACCESS_GATE_REQUEST_USER=access-request
Optional:
TELEGRAM_APPROVAL_CHAT_ID=<telegram-chat-id>
ACCESS_GATE_AGENT_NAME=Example Access Bot
ACCESS_GATE_OPERATOR_NAME=Example Operator
ACCESS_GATE_ICON=🖥️
ACCESS_GATE_DISPLAY_TZ=Europe/Berlin
ACCESS_GATE_MAX_MINUTES=240
ACCESS_GATE_SUDO_SESSION_MINUTES=30
ACCESS_GATE_BUTTON_TTL_SECONDS=900
TELEGRAM_APPROVAL_CHAT_ID is the group or DM where SSH/sudo approval questions are posted. Leave it unset to send approval questions to the TELEGRAM_ALLOWED_USER_ID DM.
When an SSH window is open, "SUDO session" approval follows that SSH window. If the operator adjusts the SSH expiry, an active sudo session is synced to the adjusted expiry.
ACCESS_GATE_COMMAND defaults to the lower-case server name with - replaced by _, because Telegram slash commands are friendlier with underscores.
The token file must stay root-only:
sudo chown root:root /etc/hakalya-access-gate/bot.env
sudo chmod 0600 /etc/hakalya-access-gate/bot.env
sudo systemctl enable --now hakalya-access-bot.service
The installer creates this baseline:
/etc/hakalya-access-gate/ root:root 0755
/etc/hakalya-access-gate/bot.env root:root 0600
/opt/hakalya-access-gate/ root:root 0755
/opt/hakalya-access-gate/state/ root:root 0755
/opt/hakalya-access-gate/keys/ root:root 0755
/opt/hakalya-access-gate/keys/hakalya-approval.pub root:root 0644
/home/access-gated/.ssh/ access-gated:access-gated 0700
/home/access-gated/.ssh/authorized_keys access-gated:access-gated 0600
/home/access-request/.ssh/ access-request:access-request 0700
/home/access-request/.ssh/authorized_keys access-request:access-request 0600
/usr/local/sbin/hakalya-gated-allow root:root 0755
/usr/local/sbin/hakalya-gated-revoke root:root 0755
/usr/local/bin/hakalya-access-bot root:root 0755
/usr/local/sbin/hakalya-approved-sudo root:root 0755
/usr/local/bin/hakalya-sudo root:root 0755
/usr/local/bin/sudo root:root 0755
/usr/local/bin/hakalya-request-access root:root 0755
/usr/local/sbin/hakalya-request-access-root root:root 0755
/etc/sudoers.d/hakalya-gated root:root 0440
/etc/sudoers.d/hakalya-request root:root 0440
Updating an existing host
Use install-host.sh only for first bootstrap. For normal code updates, run the updater from this repo:
./update-host.sh \
--host example-server \
--server example-server \
--command example_server \
--approval-chat-id -1001234567890 \
--icon 🖥️
The SSH window must already be open, or the command must be run from a console/root path. If hakalya-sudo has no active sudo session, it will ask through Telegram before applying the update.
The updater:
- runs local shell and Python syntax checks,
- uploads the scripts to
/tmp/hakalya-access-gate-updateby default, - installs them through
hakalya-sudo, - records the SSH login user as
ACCESS_GATE_WORKING_USERunless--working-useris given, - optionally updates
TELEGRAM_APPROVAL_CHAT_ID,ACCESS_GATE_SERVER_NAME,ACCESS_GATE_COMMAND, andACCESS_GATE_ICON, - restarts
hakalya-access-bot.service, - verifies remote hashes against the local repo files.
It does not recreate users, keys, sudoers, or systemd units. If the host is not installed yet, use install-host.sh first.
For repeatable local deploys without committing hostnames, chat ids, server names, or other environment-specific values, use the env wrapper:
ACCESS_GATE_DEPLOY_HOST=example-server \
ACCESS_GATE_SERVER=example-server \
ACCESS_GATE_COMMAND=example_server \
ACCESS_GATE_WORKING_USER=access-gated \
./deploy-env.sh
Or with just:
ACCESS_GATE_DEPLOY_HOST=example-server \
ACCESS_GATE_SERVER=example-server \
ACCESS_GATE_COMMAND=example_server \
just deploy
You can copy deploy.env.example to a private local env file and source it before running ./deploy-env.sh.
Optional env vars: ACCESS_GATE_APPROVAL_CHAT_ID, ACCESS_GATE_ICON, ACCESS_GATE_WORKING_USER, ACCESS_GATE_SUDO_WRAPPER, and ACCESS_GATE_REMOTE_STAGE.
The deploy wrapper never reads or deploys TELEGRAM_BOT_TOKEN; tokens stay in /etc/hakalya-access-gate/bot.env on each host.