Issue your first access token
In this tutorial you will add a named upload token to a hosted index, scope it to a set of projects with a glob, and publish through it with twine. You will publish one package the token covers and watch a second one, outside the scope, get turned away. It takes about ten minutes and builds on getting started.
The token is enforced through HTTP Basic auth: the same __token__:<token> convention pip and twine already use for
pypi.org, so no new client and no login step is involved. A hosted OCI index enforces the same
model on docker push; this walkthrough uses PyPI because twine needs the least setup.
The goal
A hosted index that a CI job publishes to. The job's token may write projects named team-* and nothing else, so a
mistyped or malicious upload to another name fails at the door instead of landing in your store.
Write the topology
Save this as peryx.toml:
data_dir = "peryx-data"
[[index]]
name = "pypi"
cached = "https://pypi.org/simple/"
[[index]]
name = "hosted"
hosted = true
[[index.access_token]]
name = "ci"
secret = "ci-secret"
projects = ["team-*"]
actions = ["write"]
[[index]]
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"
The [[index.access_token]] table names one credential the hosted index accepts. secret is the password a client
presents. projects is a list of globs, where * stands for any run of characters; team-* covers every project whose
normalized name starts with team-. actions lists what the token may do, from read, write, and delete.
Start peryx:
peryx serve --config peryx.tomlPublish a project the token covers
Build a small package named team-widgets (reuse the steps from getting started, changing
the project name), then publish it to the virtual index's route. peryx accepts any username; the token is the password,
matching the __token__ convention:
twine upload --repository-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p ci-secret dist/*
The upload succeeds. peryx matched the password against the ci token, saw the normalized project name team-widgets
against the token's team-* glob, and stored the file in the hosted layer.
Watch a project outside the scope get refused
Now build a package named other-widgets and try the same command:
twine upload --repository-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p ci-secret dist/*
This one returns 403 with token does not grant this action. The credential is valid, so peryx does not ask you to
authenticate again; the token simply holds no grant for a project named other-widgets. Scope is enforced on the name
the upload declares, so a token cannot reach past the projects it was issued for.
The one-token shortcut
If you want a hosted index that a single trusted token may write and delete anywhere, you do not need an
[[index.access_token]] table at all. The older upload_token key still works and stands for exactly that, one
credential granted write and delete over every project:
[[index]]
name = "hosted"
upload_token = "hosted-secret"
An index configured this way behaves as it did before peryx had a token model. Reach for [[index.access_token]] when
one blanket credential is too much, which is the moment a scoped grant earns its keep.
Where next
- The tasks behind this walkthrough, one recipe each: control access to an index
- Every key and its default: authentication and access control
- Why peryx holds its own tokens and never forwards yours upstream: client auth versus upstream credentials