Documentation

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.

On this page