Control access to an index
Each task below is self-contained. They share one model, described in full under authentication and access control; this page is the cookbook.
Write and delete authorization runs through the model today, enforced as HTTP Basic auth on a PyPI upload and on a
docker push. The read side (anonymous_read) is recorded now and enforced when the read challenge ships, so you can
declare the policy ahead of the enforcement.
Scope a token to some projects
Add an [[index.access_token]] table to the hosted index. projects is a list of globs; actions is any of read,
write, and delete.
[[index]]
name = "hosted"
hosted = true
[[index.access_token]]
name = "ci"
secret = "ci-secret"
projects = ["team-*", "shared/tools"]
actions = ["write"]
A client presents the secret as its Basic password (-u __token__ -p ci-secret for twine, -p ci-secret for
docker login). The token may write any project matching team-* or the exact name shared/tools, and nothing else. A
write to another name returns 403. Give a token actions = ["write", "delete"] if the same credential should also
remove releases. An index can carry several [[index.access_token]] tables; each needs a distinct name.
Let one token write everywhere
For a hosted index that a single trusted credential may write and delete across every project, the upload_token key is
the whole configuration:
[[index]]
name = "hosted"
upload_token = "hosted-secret"
This is sugar for one token granted write and delete over *. Use it when you do not need per-project scope; reach for
[[index.access_token]] the moment you do.
Declare an index's reads private
By default any client may read an index. Set anonymous_read = false to require a credential to read it:
[[index]]
name = "internal"
hosted = true
anonymous_read = false
[[index.access_token]]
name = "reader"
secret = "reader-secret"
projects = ["*"]
actions = ["read"]
The flag records that this index's reads are not open, and a read-granting token expresses who may still read it. Read enforcement arrives with the read challenge; until then the flag is recorded but reads are served openly, so treat this as declaring the policy ahead of the gate.
Close a whole server
Setting anonymous_read = false on every index is tedious and easy to forget on a new one. The [auth] table flips the
default instead:
[auth]
default_anonymous_read = false
Every index now defaults to private reads, and an index that should stay open opts back in with anonymous_read = true.
One knob makes a fully private server the default and a public index the exception.
Keep a secret out of the config file
Every secret key has a _file sibling that names a path to read the value from, so the config file holds no plaintext:
[auth]
signing_key_file = "/run/secrets/peryx-signing-key"
[[index]]
name = "hosted"
upload_token_file = "/run/secrets/hosted-token"
[[index.access_token]]
name = "ci"
secret_file = "/run/secrets/ci-token"
projects = ["team-*"]
actions = ["write"]
peryx reads each file once at startup and trims trailing whitespace, so a file written by echo or mounted by an
orchestrator works unchanged. An empty file is a startup error. Set a key or its _file sibling, never both. This
composes with Docker and Kubernetes secret mounts under /run/secrets, systemd LoadCredential, and files rendered by
Vault or SOPS, covered in
client auth versus upstream credentials.
Related
- The full model and every key: authentication and access control
- A start-to-finish walkthrough: issue your first access token
- The
[auth]and[[index.access_token]]keys in context: configuration