← All essays· System Design

License Activation for Private Deployments

A license-activation scheme for private deployments: activation code + static Refresh Token + in-memory random instance-id detection + short-lived signed License, turning piracy from cheap, invisible and unattributable into detectable, containable and attributable. Threat model, evolution path, activation/recovery flow, and the full reasoning Q&A.

Threat model (two tiers) —— name what we defend against first, then the conclusion.

TierMethodResult
Tier 1
Copy, unmodified
Copy / clone the whole running system (incl. RT) —— over-deployment / whole-environment cloning / extract and reuse the RT✅ Detectable (concurrent in-memory instance-id detection + frequency)
Tier 2
Code change / forged trust
Modify the agent/service, patch out checks, pin the instance-id, forge agent↔business-service trust❌ Not defensible by software alone (legal / contract)

Conclusion · activation code + static RT + in-memory random-id detection + short-lived signed License

A simple structure that handles the copy-without-reversing-code (Tier-1) threat. An activation_code activates once → you get a static refresh token (no entitlement); each refresh returns a short-lived signed License File (SLF, carrying the full entitlement) and extends the RT TTL when needed, with no RT rotation. Copy detection relies on a random instance-id generated in memory (never persisted): it rides every refresh, and when the cloud sees one RT with multiple concurrent, persistent instance-ids it flags license reuse → [alert → confirm → revoke].

activation code (one-time)
  + static Refresh Token (identity only / refresh extends TTL)
  + Short License File (full entitlement snapshot)
  + in-memory random instance-id (unreadable by clones, enables copy detection)
  + audit + cloud-side HMAC verifier (RT never leaks in the cloud)
———
emergency "rotation" = re-activation → new activation code

0Design premises

Load-bearing premises

  1. Assume the trust channel between the private-deployment verification service and the business services is not breached, and the License does not leak inside the private environment. Business services only accept entitlement pushed by the local license agent; they never verify a bearer license file themselves. Fanning a signed license/entitlement out to N clusters would require all N to point at one verification agent —— i.e. forging agent↔business-service trust —— which this premise excludes.
  2. The license agent is a singleton within a cluster (HA via leader). So "one cluster = one live agent = one instance-id", which makes the in-memory instance-id a clean copy-detection signal.

Breaks when: a signed license can be reused (e.g. a business service starts verifying it itself) —— then you must evaluate binding the license to the cluster key (key-binding).

Evolution path (new → old)

Each step: mechanism / what it solved / what it left behind. The scheme evolved bottom (old) to top (new); each step fixes one new problem the previous created; ★ is the current scheme.

Static RT + in-memory instance-id reuse detectioncurrent

activation code bootstraps → obtain a static RT (identity only) + first SLF (full entitlement); each refresh extends the RT TTL + issues a new SLF; the agent generates an in-memory random instance-id at startup and sends it on every refresh; the cloud sees one RT with multiple persistent instance-ids → detects reuse.

solvedCloses "RT leak / clone hard to notice": the in-memory instance-id catches clones (a clone can't read the in-memory id), cleaner than deployment_id; simplest structure, no rotation state machine / grace / bricking; tolerates restart & rolling-upgrade overlap.
leftRequires a singleton agent (HA leader election); patching instance_id to break it is a Tier-2 attack.

Activation Code for Refresh Token

A one-time activation code is redeemed once → refresh token; afterwards the RT refreshes to fetch SLFs.

solvedNarrows leakage on the out-of-cluster distribution path (email/contract/ticket/installer); the RT is only ever minted inside the cluster.
leftThe RT is still a long-lived token; an in-cluster leak/clone is hard to notice —— covered by ★ in-memory instance-id detection.

Key for short-lived License (SLF)

The license file becomes a short-lived (day-scale) full-entitlement snapshot, refreshed periodically (+ on-demand triggers).

solvedEnables early revoke, up/down-scoping, dynamic entitlement, fail-close; each SLF is a full snapshot, so there is no entitlement state to manage (see Ⅴ Q8).
leftThe Key is a long-lived token, reusable if leaked —— narrowed by ④ activation code.

Key for License File

Ship a long-lived key at deploy time; the client exchanges it for a license file from the cloud.

solvedConvenient online renewal —— no manual yearly file swap.
leftThe Key is a long-lived token; within its validity you can't revoke/downscope —— solved by ③ short lease.

Static License File

The cloud issues a static signed file; the client verifies it locally with a built-in public key.

solvedSimplest; fully usable offline.
leftRenewal means swapping the file by hand; no online revoke —— solved by ② key-for-file.

Key insights & Q&A (short)

This is the conclusions layer; the full reasoning behind each is in Ⅴ below.

Core invariant

One license = one RT = one live consumer. "Whole-environment cloning" and "copying the RT" are two ways to create a "second consumer". Use the in-memory instance-id to detect that "second consumer".

The in-memory instance-id is the load-bearing duplication signal

The agent generates a random id in memory at startup —— never persisted, sent on every refresh. A clone can't read it → the clone generates a new id → one RT with multiple concurrent persistent ids = duplication. Cleaner than deployment_id (inherited by a clone, identical to a migration).

authN / authZ split

RT = pure identity (long-lived, no entitlement), SLF = full entitlement snapshot (short-lived, signed). It's OAuth's refresh token + access token model. Duplication detection lands cleanly in the authN layer, decoupled from entitlement.

Short SLF = stateless full entitlement

Each SLF is a self-contained signed snapshot → change/revoke/downscope is trivial (issue the next number), the client is stateless about entitlement, one signature is tamper-proof, audit is free. Avoids the base+delta reconciliation of "long file + heartbeat".

A static RT is revocable —— no "permanent leak"

Refresh only extends the TTL and the value never changes; but churn / account expiry / confirmed theft can all be revoked server-side, fail-close within one TTL. Concurrent reuse is caught by the instance-id —— no need to rotate/mutate the token (that only adds a state machine and bricking risk with no new coverage).

activation code = the outside-link seam

Keep the durable credential off the outside links (email/contract/installer): a leaked outside link is either dead after the customer redeems it, or becomes a loud "activation conflict" if a thief redeems first. It also gives pre-handoff (delivery chain) vs runtime (inside the customer) attribution.

detection-first, never stop on non-detection

HA without an election, or a rolling upgrade, briefly produces multiple ids → use an overlap tolerance window + "alert → accumulate/confirm → revoke". Don't silently stop service for fragile small customers. It never bricks —— it only tolerates de-duplication.

Why can the RT be static?

Because duplication is detected by the in-memory instance-id, not by token changes exposing reuse. It drops the whole rotation state machine / grace / bricking.

Does a leaked static RT stay valid forever?

No. churn / account expiry / confirmed theft can all be revoked, fail-close within one TTL. Concurrent reuse is caught by the instance-id.

Why RT + short SLF instead of a long License File + heartbeat?

Each short SLF is a full signed snapshot → no entitlement state to manage, changes are trivial; the RT therefore degenerates to pure identity (authN/authZ split). A long file needs base+delta reconciliation to adjust dynamically.

How do instance-id and fingerprint relate?

Separate: fingerprint is a forgeable soft signal; instance-id is the load-bearing duplication signal (in memory, unreadable by clones).

Do we still need the activation code?

Yes. Mainly so a durable credential never leaks outside the cluster. It's orthogonal to the detection mechanism.

Activation / recovery flow

Normal activation is a one-time bootstrap; recovery goes through re-activation. Core invariant: a license has exactly one live activation code + one live RT chain at any moment.

Normal activation (first time)

1
After purchase the cloud issues an activation code: high-entropy, short-lived, bound to customer/license, manually revocable, non-enumerable, leaking no internal info.
2
The customer redeems it via CLI. Under HA a singleton/leader agent performs a single redeem.
3
The cloud validates (single-use / not expired / not revoked) → mints a static RT + first short SLF, binds the account, records the redeem context (time/IP/source) for attribution → the activation code is voided immediately.
4
The agent stores the RT in a shared Secret (readable by HA replicas), generates an in-memory instance-id, and starts the refresh loop (extend RT TTL + new SLF).

Recovery / re-activation (two modes)

A
Recovery mode (benign): RT lost (Secret deleted, etc.) → revoke the old code, issue a new one, the client redeems the new code to swap in a short cert, avoiding downtime.
B
Revocation mode (confirmed theft / emergency "rotation"): issue a new code and immediately revoke the old RT; the old credential dies within one TTL.
!
Both modes work the same way —— revoke the old activation code first and converge to "one live chain". Issuing a new code is the only "rotation" entry point; there is no silent server-side rotation.

Leak signal at activation

Re-redeeming an already-consumed activation code → returns a clear error + is recorded as a potential outside-link leak (with context). A thief who redeems first makes the legitimate customer's activation fail —— turning a silent outside-link leak into a loud, attributable conflict. The activation code's TTL should match the customer's real deployment lead time, with an easy re-issue path so normal customers aren't blocked by expiry.

Details

protocol = touches the interaction protocol (exchanged fields / endpoint semantics / state machine), review carefully; impl/ops = does not affect the interaction flow.

In-memory instance-id semantics protocol

The agent generates a random id at startup, in memory only —— never persisted, never written to the Secret; carried on every refresh. The cloud judges duplication by "the number of distinct, concurrent, persistent ids per RT within a time window". Requires a singleton agent, with an overlap tolerance window for restarts / rolling upgrades. (detection core)

re-activation / emergency rotation protocol

See Ⅲ. Both modes (recovery / revocation) revoke the old code first + converge to one live chain. Invariant: one license = one live activation code + one live RT chain.

activation code semantics protocol

Redeem endpoint: single-use, dies after redeem, bound to customer/license, rate-limited, non-enumerable. Re-redeeming a consumed code → error + leak hint. Records the redeem context for attribution.

RT (refresh token) renewal / revoke protocol

Each refresh extends the RT expiry server-side (value unchanged) and returns a new SLF. revoke = the server refuses to renew (account expiry / churn / confirmed theft) → fail-close within one TTL.

SLF (short License File) payload protocol

The signature covers everything: license_id / customer_id / product_id / entitlement / lease_id / sequence / issued_at / not_before / expires_at. The client verifies the signature + product/license match + time window + non-decreasing sequence, then uses and discards it, replacing the whole file. Ed25519 recommended.

Old-license replay protocol

The SLF carries a monotonically increasing sequence; the client stores last_accepted_sequence and rejects anything smaller. A whole-machine snapshot rollback rolls the sequence back too → software alone can't fully prevent it; short validity + being online + audit shrink the window.

Clock rollback protocol

Protocol: the SLF's issued_at / expires_at use server time. Every restart must refresh from the server; during runtime use time.Since (a monotonic clock).

Offline license verification protocol

The license is bound to the kube-system namespace UID.

fingerprint / deployment_id protocol P1

Requests carry client_info like deployment_id / client_version. It's a forgeable soft signal (reduces false positives, spots migrations, feeds audit), not load-bearing —— the load-bearing duplication signal is the in-memory instance-id.

DB leak impl/ops

The cloud never stores the raw RT, only an HMAC(server_secret, raw_rt) verifier; server_secret lives in KMS, in a different permission domain from the DB, versioned/rotatable.

Log leak impl/ops

Tokens carry a prefix for prefix-based redaction; all logs / traces / metrics / crash dumps are scrubbed; errors return only error code + request_id.

Full-context Q&A (detailed · collapsed)

This section keeps the full reasoning chain, so future collaborators/agents understand "why this scheme, and how each fork was ruled out".

Q1What is the threat model and goal?

The goal is not absolute anti-cracking, but making piracy detectable, containable, attributable. Under the design premises (§0), threats converge to two tiers: Tier 1, copy without modification (clone the running system) → detectable; Tier 2, code change / forged internal trust → not defensible by software, needs hardware attestation / legal contracts.

Frequency floor: N independent clusters must each refresh ≥1×/validity to avoid fail-close → combined ≥N×, and you can't hide that behind a coordinator.

Q2Why can the RT be static? (no mutate / no rotation)

Because duplication detection doesn't depend on token changes but on the in-memory instance-id: the agent generates a random id in memory at startup, never persisted, sent on every refresh. A clone can't read the in-memory id → the clone generates a new id → one RT with multiple concurrent persistent ids = duplication. Cleaner than deployment_id (inherited by clones, identical to a migration); bypassing it needs patching the agent to pin the id = Tier 2.

Since detection is already provided by the instance-id, mutating the token (rotation) only adds a state machine / grace / bricking-on-lost-response, with no new coverage —— so the RT is static and refresh only extends the TTL. In essence the instance-id is a "lightweight unsigned PoP" that only detects, doesn't authenticate —— cleartext over TLS is enough.

Q3Are anti-duplication and anti-extraction the same thing?

Core invariant: one license = one live RT chain = one live consumer. Cloning and extraction both just "create a second consumer"; coverage is equivalent (same "multiple instance-id" signal) → achieve anti-dup and anti-extraction follows.

Q4Customer profile: both kinds exist —— should we bother protecting?

Large customers are bound by brand + contract + audit and basically don't touch it. The threat concentrates in small customers: over-deployment / whole-environment cloning = Tier 1, exactly this scheme's sweet spot. Hence detection-first, revoke only after confirmation —— small customers run rough ops (no HA / manual migration / snapshot restore), so don't silently stop service and hurt them.

Q5With the agent holding the RT, is the threat "extraction" or "cloning"?

The agent hosts refresh; most customers never touch the RT. The dominant piracy mode isn't extracting the RT, it's whole-environment cloning —— the RT is copied along with the Secret. And the in-memory instance-id catches exactly that: the clone can't read the in-memory id, the cloned agent generates a new id → one RT with two concurrent ids → duplication. Actually patching the agent to pin the id is Tier 2.

Two corrections: legit customers' friction is lower than feared (the agent hosts it robustly); the RT's accidental-leak surface is smaller than assumed (only inside the cluster).

Q6Do we still need the activation code? Is its value attributing the leak source?

The main reason isn't attribution —— it's keeping the durable credential off the outside links. Drop it and you'd bake the RT into the shipped package; once the package leaks on an outside link and is used alone (a thief deploys first / the legit party isn't live yet), the instance-id can't help (it only catches concurrency). The activation code makes the outside link carry only a one-time code: dead once the customer redeems it; if a thief redeems first, the customer's activation fails → instant exposure.

Attribution is a free second benefit: activation is a discrete node that splits a leak into pre-handoff (delivery chain) vs runtime (inside the customer). But discount it twice: an activation leak could be the customer's procurement forwarding it; runtime duplication could be an external attacker who breached the customer; and it only holds if you recorded the redeem context → a strong prior, not proof.

Q7Is a static RT being "valid forever" a problem?

No —— "valid forever" overstates it. Static = the value doesn't change, but it's revocable: unsubscribe / account expiry / confirmed theft, the server refuses renewal, fail-close within one TTL. For duplication (concurrent reuse), the real threat, static RT + revoke + instance-id already covers it fully; the residual you worried about is a non-duplication corner (a thief using it alone, the account still paying, the legit party never coming online) —— neither reuse nor really plausible. To passively expire an old credential → go through re-activation.

Q8Why RT + short SLF instead of a long License File + heartbeat?

The core benefit is stateless full-entitlement delivery. Each SLF is a self-contained, signed, time-bounded full snapshot. Hence: entitlement change/revoke/downscope is trivial (issue the next number, no delta protocol); the client is stateless about entitlement (holds only the current SLF + last_accepted_sequence, replaced wholesale); one signature is tamper-proof (no signing and ordering a stream of deltas); audit is free (each issuance is one record with the snapshot + sequence).

Versus long file + heartbeat: to support dynamic entitlement the heartbeat must carry updates → maintain a base + a stream of mutable deltas, reconcile both sides, sign and order each —— exactly the state SLF avoids; extending only the validity can't do dynamic adjustment.

So the RT degenerates to pure identity (no entitlement) = authN / authZ split = OAuth's refresh token (identity) + access token (signed claims). Three notes: ① state doesn't vanish, it moves to the server (source of truth), leaving the client minimal; ② "short" gives timely revoke/change, "full" gives statelessness —— a pair; ③ the cost = SLF TTL = the lower bound on revoke latency (an issued SLF can't be recalled, you just stop re-issuing —— symmetric with the heartbeat model). The layering dividend: duplication detection (RT + instance-id) lands cleanly in the authN layer, decoupled from entitlement.

Q9The activation / recovery flow and its edges?

Normal activation, see Ⅲ: one-time code → singleton/leader agent redeem → cloud validate (single-use / not expired / not revoked) → mint static RT + first SLF + record redeem context → void the code → agent stores the RT in the shared Secret, generates the in-memory instance-id, starts the refresh loop.

Redeem conflict = leak signal: re-redeeming a consumed code → clear error + recorded as a potential outside-link leak (a thief redeeming first makes the legit customer's activation fail → loud exposure).

Recovery / re-activation, two modes: recovery (RT lost; the old chain survives until the new code is redeemed, avoiding downtime) vs revocation (confirmed theft; kill the old chain immediately). Both revoke the old code first and converge to one live chain. Edges: RT lost / code expired → re-activation (accepts manual intervention, rare); the code TTL matches the deployment lead time + keeps a re-issue path.

Appendix

A · What it defends / doesn't (scope boundary)

Why: a clear boundary manages expectations and avoids over-investment, and it's the basis for the accountability clauses in the contract / license terms.

Defends or mitigates (Tier 1): long-term copying of static files, long-lived key leaks, RT re-distribution, whole-environment cloning / concurrent reuse, old-license replay, simple clock rollback, dynamic entitlement adjustment, early revoke, over-authorized multi-cluster, DB leak grabbing a token.

Can't fully defend (Tier 2): code change / patching out local checks, pinning the instance-id, forging internal agent↔business-service trust, continuously syncing the latest RT to an attacker, whole-machine snapshot rollback without trusted storage, a high-privilege cloud insider, an authorized customer actively reselling the latest Secret.

B · Glossary

Why: fast alignment for future agents / new teammates; some terms have specific meanings.

activation code one-time bootstrap credential · refresh token (RT) in-cluster static credential, identity only, refresh just extends TTL · SLF (short License File) a short signed lease carrying the full entitlement snapshot, consumed by services · in-memory instance-id the instance identity the agent generates at startup, never persisted, sent on every refresh —— the load-bearing duplication signal · duplication detection one RT presenting multiple concurrent persistent instance-ids · re-activation re-requesting an activation code (revoking the old chain first), doubling as emergency "rotation", in recovery / revocation modes · Tier 1 / Tier 2 copy-class (detectable) / code-change-class (not defended).

License Scheme Design Reference · 2026-07-08 · The point: make piracy no longer cheap, no longer invisible, no longer hard to attribute.