Tokens¶
A token is the bot. Anyone holding it can read every message the bot receives and send as it, and Telegram has no way to tell that apart from the real thing — so where the token is kept is a decision, and since 5.0 it is one this package can be told about.
It matters most where the tokens are not yours: a deployment whose clients connect their own bot holds a credential per client. Dynamic bots is where those arrive from, and Admin is the permission one is read behind.
Where a token comes from¶
Two places, and they are not the same kind of secret:
TELEGRAM_BOT_DEFAULTS['TOKEN']or aTELEGRAM_BOTSsection — a token in the deployment's own configuration, usually from an environment variable. Nothing here stores it; it lives wherever the settings do.- A
TelegramBotrow — a bot a client connected through the project's own interface. This is the one a database dump carries.
TOKEN_STORAGE¶
Every read and write of a row's token goes through one seam, so a project that turns encryption on has no plaintext path left behind:
TELEGRAM_BOT_DEFAULTS = {
'TOKEN_STORAGE': 'django_aiogram.crypto.FernetTokenStorage',
'TOKEN_ENCRYPTION_KEYS': [os.environ['TG_TOKEN_KEY']],
}
The default is django_aiogram.tokens.PlainTokenStorage, which writes the value as it was
given. That is a real answer rather than an oversight: where the database is already the trust
boundary, what protects the token is its access control and the admin permission on the
column — and a base install then never imports cryptography.
The encrypting one needs the extra:
Both settings belong to the process rather than to a bot — a section that sets one is refused
by E053. E062 reports a storage that cannot be built, and E063 an encrypting storage
with no keys; both are read at boot, so manage.py check answers rather than the first row
that could not be read.
Turning it on for a table that already has tokens¶
The storage reads a value it did not write — every plain token from before — as it is, so the order is a settings change, a deploy, and then this walk. Nothing is down in between, and a row the walk has not reached yet is still readable by every process.
--dry-run says what it would write. A row whose column is already what the storage writes is
left alone, because updated_at is the watermark every supervisor polls and a table-wide bump
would have every container re-read every bot for nothing.
A row whose token changed while the walk was running — an admin rotating that bot's credential at the same moment — is left with the new token and reported: the write is conditional on the value that was read, so a rewrapped copy of the old token cannot replace it.
Rotating a key¶
The keys are listed newest first: the first one encrypts, every one of them decrypts. A list or
a tuple — a set has no order to be first in, and E063 refuses one. A single key may be a
bare string.
TELEGRAM_BOT_DEFAULTS = {
'TOKEN_STORAGE': 'django_aiogram.crypto.FernetTokenStorage',
'TOKEN_ENCRYPTION_KEYS': [os.environ['TG_TOKEN_KEY_NEW'], os.environ['TG_TOKEN_KEY_OLD']],
}
- Add the new key in front and deploy. Old rows still read.
- Wait until every process is running with both keys, then run
manage.py tgbot_rewrap_tokens. Each readable row that is not already current is rewritten under the new key — and a process still running with the old list alone cannot read a rewritten row, so on a rolling deploy the walk has to wait for the rollout to finish rather than run beside it. - Read the counts it printed before dropping anything: a row it reported as unreadable or as changed under the walk is a row still holding what it held, and dropping the old key then is dropping the only key that reads it. Run the walk again.
- Drop the old key from the list and deploy again.
Doing step 3 before step 2 is the one mistake that costs data: the rows written under the old key stop being readable, and the bots in them are reported and left out rather than served — one bot's credential is one bot's problem, and the healthy bots keep running. Put the key back and run the walk.
A new key is Fernet's own:
What is not a secret store¶
This encrypts a column. It does not hide the token from a process that serves the bot — that process has to talk to Telegram — and it does not help if the key sits in the same dump as the rows. Keep the key where the deployment's other secrets are.
Nothing writes a token into the event log: wire/payloads.py redacts token-shaped strings
anywhere in a recorded value, which covers the API URL aiogram puts into its exception
messages. See Event log.