gnap-caep-risc
A server-side GNAP client. The key is registered by value and proved with mutual TLS, an HTTP message signature, or a detached JWS. Access tokens are key-bound; a bearer token is refused. One Shared Signals poll stream carries CAEP and RISC events.
The private key stays in this process. The same gnap.json shape as the Node library loads: the JSON keys are identical.
Build and tests
docker build --target test .
The test stage runs pip install --no-cache-dir ".[dev]" && pytest in python:3.11-slim. GitHub Actions on a push uses that command. Do not run pytest on the host. The demo image does not run the suite.
Those tests use an in-process stub. They do not start iya-sts.
python3 scripts/live_iya_sts.py is the live half. It copies this checkout to a scratch build context, generates the client key and runs the install script there (which writes the tlsCa chain), then builds this demo image from it, checks the image carries the chain and no key, starts the demo with no CA mount and TLS_CA_REFRESH=0, signs in, and requires a credential-change event and a session-revoked event on GET /live-status. The second event ends the local session. When IYA_STS_URL is unset it starts the latest main branch of iya-sts in memory mode, with persistence and clustering off. It uses the host's Python and Docker and does not import this library. It binds two high ports and never binds 3000 or 8081. The nightly workflow runs the unit suite and this script once every 24 hours.
A pull request merged into main publishes the package to PyPI after the test workflow succeeds on that commit. A direct push does not. There is no API token: PyPI Trusted Publishing trades the workflow's GitHub OIDC token for a short-lived upload token, from the pypi environment, and PyPI holds the publisher for this repository and publish.yml. A version already on PyPI stays there; the next release is a new version in pyproject.toml (and VERSION).
The iya-sts trust chain (tlsCa)
tlsCa in demo/gnap.json names the iya-sts TLS listener CA trust chain the library trusts. The default is generated/idp-ca.pem, relative to demo/. The value may also be file:<path>, env:<NAME>, or the PEM itself. The library adds every certificate in it to the default trust store.
scripts/configure_iya_sts.py, the install script, writes that file. It saves the whole chain iya-sts publishes at GET /tls/server-certificate (leaf, TLS Issuing CA, Intermediate, Root), or the chain from the TLS handshake when that path does not answer. It prints the path, the trust anchor's subject and its SHA-256 fingerprint. Compare the anchor with the Root CA on iya-sts's PKI page.
The demo image build copies that file into the image at /app/demo/generated/idp-ca.pem, so the image trusts that iya-sts with no bind mount and no fetch. Run the install script before you build the image. Rebuild the image after running the install script against a different iya-sts. .dockerignore admits only that file from demo/generated. The client key and certificate stay out of the image; compose mounts demo/generated for them. A build with no chain file yet (a fresh clone, CI) still succeeds; the image then has no tlsCa file.
The mechanism is COPY demo demo plus .dockerignore's demo/generated/* and !demo/generated/idp-ca.pem. Do not replace it with a COPY that names the file or globs for it: when the file is absent, BuildKit fails on the missing demo/generated and the classic builder fails with "no source files were specified". A directory COPY of demo/ (which always exists) succeeds in both.
The container entrypoint also refreshes the file from the authorization server at start, because a development iya-sts makes a new CA when it restarts. A failed refresh keeps the file from the build. TLS_CA_REFRESH=0 turns the refresh off. python scripts/trust_chain.py --config demo/gnap.json refreshes it by hand.
Three calls
from gnap_caep_risc import GnapClient
gnap = GnapClient.from_file("gnap.json")
started = gnap.request_grant()
done = gnap.finish_redirect(query, [started.pending.id])
request_grant returns a redirect and a pending grant. finish_redirect checks the interaction hash against grants this process started, then continues the grant with the key proof. Pass the pending ids this browser was given as allowed_ids.
resolve_config and read_config_file load the same document without starting a grant.
Signals
signals omitted or true keeps CAEP and RISC on. The resolved configuration omits the key, fills ssf from ssf.transmitter or transmitterIssuer, and asks for ssf.read and ssf.manage when access does not say otherwise.
signals: false does not start the Shared Signals receiver, does not require transmitter metadata, and drops ssf.read and ssf.manage from the GNAP access. The resolved configuration includes signals: false. If those two rights were the only ones, configuration fails and asks for another right the authorization server grants. Any other value fails with signals must be true or false.
ssf.pollIntervalMs is the poll interval in milliseconds.
Demo
docker compose up --build
PORT defaults to 3000 and is the bind port. Loopback finish and display URLs follow PORT. Any other host is left as written. PUBLIC_URL or --public-url replaces the origin and keeps the path. GET /healthz returns ok. The home page has a sign-in control; /login starts a grant, /callback finishes it, and POST /logout signs out. GET /live-status is JSON: signedIn, subject, the receiver state, and the Shared Signals events this process has accepted (type and jti only). It does not include tokens. Do not bind port 3000 or 8081 while something else is already using them.
The shipped demo/gnap.json leaves signals on by omitting the key. Key material is file: or env: and is not in the tree. Generate a key inside the demo image, then register the application:
docker compose build
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD/demo:/app/demo" \
gnap-caep-risc-python-demo \
gnap-caep-risc generate-key --config demo/gnap.json
ADMIN_API_CLIENT_SECRET=… python scripts/configure_iya_sts.py --config demo/gnap.json
docker compose build
The second build bakes in the trust chain the install script saved.
The install script creates the test user gnap-demo-user. Its password is $GNAP_DEMO_USER_PASSWORD, or iya-sts generates one; the script writes a generated password to demo/generated/gnap-demo-user.password (mode 0600) and prints only that path, never the password.
--dry-run prints the admin API calls and sends nothing. When the gnap-demo container is running, its PORT wins over the shell PORT.
License
MIT. Copyright (c) 2026 Iya CyberSecurity Solutions, LLC.
Metadata
Release files for gnap-caep-risc 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gnap_caep_risc-0.1.0.tar.gz | 44.9 kB | With provenance | Details for gnap_caep_risc-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gnap_caep_risc-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for gnap_caep_risc-0.1.0-py3-none-any.whl |
Total release size: 83.4 kB
Release files / gnap_caep_risc-0.1.0.tar.gz
| Download URL | gnap_caep_risc-0.1.0.tar.gz |
|---|---|
| Size | 44.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e6cdc2366eb73a905f5c225b6811328ffcb03f2c7bb44d6dc99098705e083667
|
|
BLAKE2b-256 checksum How to use checksums |
bebb2b62e6be57f570dede6b59f287bf9f005e28f786ef35a8d99677f9906a80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / gnap_caep_risc-0.1.0-py3-none-any.whl
| Download URL | gnap_caep_risc-0.1.0-py3-none-any.whl |
|---|---|
| Size | 38.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
152ff5288dfb0b182aff3cf3112774eef4f789c410f40114aec6fa24da9bd853
|
|
BLAKE2b-256 checksum How to use checksums |
710d8624d54af2c2a6da0b8a03edf46da0acefad66beff7a6869e58b2bb1e388
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for gnap-caep-risc 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for gnap_caep_risc-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.