Licensing — generating SimpleVirt license keys
Looking for what a subscription means? See
docs/subscription.md: what a key does and does not buy, how the controller reports it, the dismissible non-subscribed notice, and the contract for the optional online validation server. This page is the vendor-side half — how to mint keys with your own signing key.
This page is about the SimpleVirt subscription, not the software licence. They are different things and only share a word. The software licence is the GNU Affero General Public License v3 or later: its full text is in
LICENSEat the root of the source tree, and on an installed appliance at/usr/share/doc/simplevirt/LICENSE. A subscription token records a commercial entitlement and a licensed-to name; it gates no capability in the product and it narrows nothing the AGPL grants you.
SimpleVirt licenses are Ed25519-signed tokens. Nothing phones home: the appliance carries only the public key and verifies a token offline. Only whoever holds the private signing key can mint one, so licensing is entirely in your hands when you run your own build.
A token looks like:
SIMPLEVIRT-<base64url(payload)>.<base64url(signature)>where the payload is {"tier":"Enterprise","to":"Acme Corp","exp":1790000000}
(exp is a Unix timestamp; a perpetual key omits it).
1. Mint a license (the common case)
You already have deploy/license/simplevirt-license.key (the private signing key,
git-ignored). Then:
make license TIER=Enterprise TO="Acme Corp" DAYS=365TIER— any string your build recognises (e.g.Standard,Enterprise).TO— the licensed-to name, shown on the appliance's Licensing screen.DAYS— validity window;DAYS=0mints a perpetual key.
The command prints one SIMPLEVIRT-… line. Paste it into the appliance under
Administration → Licensing. The host verifies it against the embedded public
key and stores it; there is no activation server and no network call.
Under the hood this is just cmd/simplevirt-license, so you can run it directly
without make — handy in CI, where the key comes from a secret rather than a
file on disk:
SIMPLEVIRT_LICENSE_KEY="$SIGNING_KEY_B64" go run ./cmd/simplevirt-license \
-tier Enterprise -to "Acme Corp" -days 365-key / SIMPLEVIRT_LICENSE_KEY is a base64 Ed25519 private key (64 bytes
decoded). The Makefile target reads it from deploy/license/simplevirt-license.key.
2. Generate a signing keypair (first-time / fork / rotation)
Do this only when you are standing up licensing for your own build, or rotating a compromised key. It replaces the vendor keypair with one only you hold.
Generate the keypair (base64, the form both halves expect):
go run ./cmd/simplevirt-keygenIf that tool is not present in your tree, the same three lines inline:
go run - <<'EOF'
package main
import ("crypto/ed25519";"crypto/rand";"encoding/base64";"fmt")
func main(){
pub, priv, _ := ed25519.GenerateKey(rand.Reader)
fmt.Println("PRIVATE (keep secret):", base64.StdEncoding.EncodeToString(priv))
fmt.Println("PUBLIC (embed): ", base64.StdEncoding.EncodeToString(pub))
}
EOFInstall the private half where the minting tools read it — this file is
git-ignored (.gitignore → deploy/license/simplevirt-license.key) and must never
be committed or baked into an ISO:
printf '%s' '<PRIVATE base64>' > deploy/license/simplevirt-license.key
chmod 600 deploy/license/simplevirt-license.keyEmbed the public half so appliances can verify tokens. Replace the constant
in internal/license/license.go:
var simplevirtPublicKey = mustDecodeKey("<PUBLIC base64>")Then rebuild the binaries and rebake the ISOs — the public key ships inside the image, so a key change is an ISO change, not a hot config:
make appliance-binaries appliance-command-binaries
make appliance-iso appliance-command-isoEvery token you minted with the old private key stops verifying the moment an appliance runs the new build. Rotation invalidates the field — plan it.
Security notes
- The private key is the whole ballgame. Anyone who holds it can issue unlimited perpetual Enterprise licenses. Keep it off the ISO, out of git, and in a secret store for CI — never in the repo, never in a layer of a Docker image.
- The appliance only ever verifies.
internal/licensehas no private key and no minting path, by design: a stolen appliance cannot forge licenses. - Tokens are not secret. A license token is signed, not encrypted — it is safe to email to a customer. Its contents (tier, licensed-to, expiry) are readable by anyone; the signature is what makes it unforgeable.
- Expiry is enforced on the appliance clock. A box with a badly wrong clock can reject a valid perpetual-adjacent key or accept a just-expired one; this is the usual offline-licensing trade-off, accepted deliberately over a call-home check.