# yama-issue-token CLI to mint customer JWTs (RS256) for the YAMA Go server's **License Server** (`/license/sign`, `/license/heartbeat`). A thin shell around [`licensing.Issue()`][issue] in the YAMA Go server — all validation (tier rules, TTL floor, `sub` required) lives there, so this tool stays in sync as the licensing package evolves. [issue]: https://github.com/yuanyuanxiang/SimpleRemoter/blob/main/server/go/licensing/server.go ## Status Personal hobby project, MIT license. Not a commercial product, no SLA. ## Build This module uses a local `replace` directive to consume the YAMA `licensing` package from a sibling checkout — `go install` from a fresh clone will fail. Build it locally: ```bash git clone https://github.com/yuanyuanxiang/SimpleRemoter.git ../SimpleRemoter # yama-issue-token expects YAMA at ../YAMA/server/go (see go.mod replace); # if you cloned as SimpleRemoter, either rename or adjust the replace line. git clone https://github.com/yuanyuanxiang/yama-issue-token.git cd yama-issue-token go build -o yama-issue-token . ``` `go.mod` currently pins: ```go replace github.com/yuanyuanxiang/SimpleRemoter/server/go => ../YAMA/server/go ``` This is kept on purpose during local development — the tool tracks the in-tree `licensing` package. A pinned-version setup may come later. ## One-time RSA keypair The License Server verifies issued JWTs with the public key. Generate once, keep the **private key on the issuer machine only**: ```bash openssl genrsa -out license_priv.pem 2048 openssl rsa -in license_priv.pem -pubout -out license_pub.pem ``` Hand `license_pub.pem` to the License Server (`YAMA_LICENSE_PUBLIC_KEY`). The private key stays with whoever is issuing tokens. ## Usage ```text yama-issue-token -priv -sub [options] Required: -priv RSA private key PEM (PKCS#1 or PKCS#8). -sub Customer ID — unique string; lands in the JWT "sub" claim. Options: -tier trial | paid (default: trial) -max Max concurrent devices (trial default: 20; paid: REQUIRED, no default) -days Token TTL in days (default: 365; minimum: 1 hour) -out Write token to this file (0600) instead of stdout ``` The signed JWT goes to **stdout** (single line, no trailing whitespace). Issuance details (sub / tier / max / expires) go to **stderr** and remain visible when stdout is redirected. ### Examples ```bash # Trial customer, 5 devices, 30 days, print to terminal yama-issue-token -priv license_priv.pem -sub acme-trial -tier trial -max 5 -days 30 # Paid customer, 200 devices, 1 year, write to file (mode 0600) yama-issue-token -priv license_priv.pem -sub acme-corp -tier paid -max 200 -out token-acme.jwt # Pipe into env var (Linux/macOS shell) export YAMA_LICENSE_TOKEN=$(yama-issue-token -priv license_priv.pem -sub acme -tier paid -max 100) ``` ## Tier semantics | Tier | `-max` default | Notes | | ------- | -------------- | ----------------------------------------------- | | `trial` | 20 | Inherits the C++ anti-proxy RTT logic on server | | `paid` | required | No default — must be supplied explicitly | ## Integration Set on the customer's Go server (RemoteSigner mode): ```bash YAMA_LICENSE_SERVER=https://license.example.com YAMA_LICENSE_TOKEN= ``` The customer's server never sees the master HMAC key — it HTTPS-POSTs to `/license/sign` for each new device login, then caches the signature for 24h (`YAMA_LICENSE_OFFLINE_HRS`). See the YAMA repo for the License Server side: [`server/go/licensing/`](https://github.com/yuanyuanxiang/SimpleRemoter/tree/main/server/go/licensing). ## Security - Private key has no business leaving the issuer host. `.gitignore` excludes `*.pem`, `*.key`, `*.jwt`, `token-*.jwt`, `license_priv*`, `license_pub*`. - `-out` writes with mode `0600`. - JWT `alg` is locked to `RS256` server-side — `alg:none` attacks are rejected. ## License MIT