[{"data":1,"prerenderedAt":779},["ShallowReactive",2],{"navigation_docs":3,"-licensing-your-key-never-leaves":136,"-licensing-your-key-never-leaves-surround":774},[4,26,47,68,89,110,123],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Getting Started","\u002Fgetting-started","1.getting-started",[9,13,17,21],{"title":10,"path":11,"stem":12},"What you are running","\u002Fgetting-started\u002Fwhat-you-are-running","1.getting-started\u002F1.what-you-are-running",{"title":14,"path":15,"stem":16},"Architecture","\u002Fgetting-started\u002Farchitecture","1.getting-started\u002F2.architecture",{"title":18,"path":19,"stem":20},"Prerequisites","\u002Fgetting-started\u002Fprerequisites","1.getting-started\u002F3.prerequisites",{"title":22,"path":23,"stem":24},"Quickstart","\u002Fgetting-started\u002Fquickstart","1.getting-started\u002F4.quickstart",false,{"title":27,"path":28,"stem":29,"children":30,"page":25},"Install","\u002Finstall","2.install",[31,35,39,43],{"title":32,"path":33,"stem":34},"Install the chart","\u002Finstall\u002Finstall-the-chart","2.install\u002F1.install-the-chart",{"title":36,"path":37,"stem":38},"Activation","\u002Finstall\u002Factivation","2.install\u002F2.activation",{"title":40,"path":41,"stem":42},"DNS and TLS","\u002Finstall\u002Fdns-and-tls","2.install\u002F3.dns-and-tls",{"title":44,"path":45,"stem":46},"Verify your install","\u002Finstall\u002Fverify-your-install","2.install\u002F4.verify-your-install",{"title":48,"path":49,"stem":50,"children":51,"page":25},"Configure","\u002Fconfigure","3.configure",[52,56,60,64],{"title":53,"path":54,"stem":55},"Data services","\u002Fconfigure\u002Fdata-services","3.configure\u002F1.data-services",{"title":57,"path":58,"stem":59},"Secrets","\u002Fconfigure\u002Fsecrets","3.configure\u002F2.secrets",{"title":61,"path":62,"stem":63},"Registry and air-gapped installs","\u002Fconfigure\u002Fregistry-and-airgap","3.configure\u002F3.registry-and-airgap",{"title":65,"path":66,"stem":67},"Integrations","\u002Fconfigure\u002Fintegrations","3.configure\u002F4.integrations",{"title":69,"path":70,"stem":71,"children":72,"page":25},"Licensing","\u002Flicensing","4.licensing",[73,77,81,85],{"title":74,"path":75,"stem":76},"How licensing works","\u002Flicensing\u002Fhow-licensing-works","4.licensing\u002F1.how-licensing-works",{"title":78,"path":79,"stem":80},"Your key never leaves","\u002Flicensing\u002Fyour-key-never-leaves","4.licensing\u002F2.your-key-never-leaves",{"title":82,"path":83,"stem":84},"What phones home","\u002Flicensing\u002Fwhat-phones-home","4.licensing\u002F3.what-phones-home",{"title":86,"path":87,"stem":88},"Revoke and recover","\u002Flicensing\u002Frevoke-and-recover","4.licensing\u002F4.revoke-and-recover",{"title":90,"path":91,"stem":92,"children":93,"page":25},"Operate","\u002Foperate","5.operate",[94,98,102,106],{"title":95,"path":96,"stem":97},"Upgrade","\u002Foperate\u002Fupgrade","5.operate\u002F1.upgrade",{"title":99,"path":100,"stem":101},"Backup and restore","\u002Foperate\u002Fbackup-and-restore","5.operate\u002F2.backup-and-restore",{"title":103,"path":104,"stem":105},"Observability","\u002Foperate\u002Fobservability","5.operate\u002F3.observability",{"title":107,"path":108,"stem":109},"High availability","\u002Foperate\u002Fhigh-availability","5.operate\u002F4.high-availability",{"title":111,"path":112,"stem":113,"children":114,"page":25},"Troubleshooting","\u002Ftroubleshooting","6.troubleshooting",[115,119],{"title":116,"path":117,"stem":118},"Install failures","\u002Ftroubleshooting\u002Finstall-failures","6.troubleshooting\u002F1.install-failures",{"title":120,"path":121,"stem":122},"Licensing failures","\u002Ftroubleshooting\u002Flicensing-failures","6.troubleshooting\u002F2.licensing-failures",{"title":124,"path":125,"stem":126,"children":127,"page":25},"Reference","\u002Freference","9.reference",[128,132],{"title":129,"path":130,"stem":131},"Helm values","\u002Freference\u002Fhelm-values","9.reference\u002F1.helm-values",{"title":133,"path":134,"stem":135},"Environment variables","\u002Freference\u002Fenvironment","9.reference\u002F2.environment",{"id":137,"title":78,"body":138,"description":767,"extension":768,"links":769,"meta":770,"navigation":771,"path":79,"seo":772,"stem":80,"__hash__":773},"docs\u002F4.licensing\u002F2.your-key-never-leaves.md",{"type":139,"value":140,"toc":760},"minimark",[141,145,149,162,167,170,178,201,205,230,253,256,270,274,285,585,588,621,644,647,684,688,691,706,709,714,718,727,733,756],[142,143,78],"h1",{"id":144},"your-key-never-leaves",[146,147,148],"p",{},"Three claims on this page. Each one is checkable against the chart you installed and the traffic you can capture.",[150,151,152,156,159],"ol",{},[153,154,155],"li",{},"The signing key is generated inside your cluster and is never transmitted.",[153,157,158],{},"The license agent can read and patch exactly one Secret, and read and update exactly one Lease. It has no other access to your cluster.",[153,160,161],{},"All traffic is outbound. The Hub never opens a connection into your cluster.",[163,164,166],"h2",{"id":165},"the-key-is-generated-in-your-cluster","The key is generated in your cluster",[146,168,169],{},"On first enrollment the license agent reads 32 bytes from the cluster's random source, derives an Ed25519 keypair from them, and stores the seed in the platform Secret in your namespace. That is the entire origin story of the key. We do not generate it, we do not escrow it, and there is no recovery path that lets us reconstruct it, because there is nothing for us to reconstruct it from.",[146,171,172,173,177],{},"What the Hub receives is the public half. Activation posts the public key. Every call after that carries a proof made ",[174,175,176],"strong",{},"with"," the private key, never the key itself.",[146,179,180,181,185,186,189,190,193,194,196,197,200],{},"If you want to confirm this from outside the code, capture the agent's egress. The activation request body contains a JWK with ",[182,183,184],"code",{},"\"kty\":\"OKP\"",", ",[182,187,188],{},"\"crv\":\"Ed25519\"",", and an ",[182,191,192],{},"x"," parameter. ",[182,195,192],{}," is the public key. There is no ",[182,198,199],{},"d"," parameter, which is where an Ed25519 private key would have to appear.",[163,202,204],{"id":203},"proof-of-possession-not-a-bearer-token","Proof of possession, not a bearer token",[146,206,207,208,211,212,219,220,223,224,229],{},"A license issued to you is bound to your key. The token carries a ",[182,209,210],{},"cnf"," (confirmation) claim, per ",[213,214,218],"a",{"href":215,"rel":216},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7800",[217],"nofollow","RFC 7800",", whose ",[182,221,222],{},"jkt"," member is the JWK thumbprint of your public key computed per ",[213,225,228],{"href":226,"rel":227},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc7638",[217],"RFC 7638",".",[146,231,232,233,238,239,242,243,246,247,249,250,252],{},"Every renewal and every heartbeat is sent under DPoP, ",[213,234,237],{"href":235,"rel":236},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc9449",[217],"RFC 9449",". The deployment presents its license as ",[182,240,241],{},"Authorization: DPoP \u003Ctoken>"," and attaches a ",[182,244,245],{},"DPoP"," header holding a short-lived proof JWT it signs with the private key. The proof binds the HTTP method, the canonical request URL, a fresh identifier, and a digest of the request body. The Hub verifies that proof against the ",[182,248,222],{}," in the license's ",[182,251,210],{}," before it does anything else.",[146,254,255],{},"The consequence is the point: a stolen license token, on its own, is inert. Whoever holds it cannot renew it, cannot check in with it, and cannot move it to another cluster, because they cannot produce a proof for it. Possession of the token is not possession of the license.",[146,257,258,259,262,263,266,267,269],{},"Rotation follows from the same mechanism. On each renewal the agent generates a fresh keypair, signs the renew request with the ",[174,260,261],{},"current"," key, and sends the ",[174,264,265],{},"new"," key's thumbprint in the signed body. The Hub rotates the ",[182,268,210],{}," onto the new key only because the holder of the old one asked it to, in a request whose body is bound into the signed proof. If the Hub's response comes back bound to something other than the key the agent presented, the agent declines the rotation and keeps signing with the key the Hub still recognizes, rather than locking itself out.",[163,271,273],{"id":272},"what-the-agent-can-touch-in-your-cluster","What the agent can touch in your cluster",[146,275,276,277,280,281,284],{},"The license agent runs under its own ServiceAccount with a namespaced Role. Not a ClusterRole. This is the whole of it, straight from ",[182,278,279],{},"templates\u002Flicense-agent-rbac.yaml",", rendered here for a release named ",[182,282,283],{},"my-mvno",":",[286,287,292],"pre",{"className":288,"code":289,"language":290,"meta":291,"style":291},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","apiVersion: rbac.authorization.k8s.io\u002Fv1\nkind: Role\nmetadata:\n  name: my-mvno-platform-license-agent\n  namespace: my-mvno\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    resourceNames: [\"my-mvno-platform-env\"]\n    verbs: [\"get\", \"patch\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    verbs: [\"create\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    resourceNames: [\"my-mvno-platform-license-agent\"]\n    verbs: [\"get\", \"update\"]\n","yaml","",[182,293,294,310,321,330,341,352,360,380,400,419,449,469,487,505,524,541,559],{"__ignoreMap":291},[295,296,299,303,306],"span",{"class":297,"line":298},"line",1,[295,300,302],{"class":301},"swJcz","apiVersion",[295,304,284],{"class":305},"sMK4o",[295,307,309],{"class":308},"sfazB"," rbac.authorization.k8s.io\u002Fv1\n",[295,311,313,316,318],{"class":297,"line":312},2,[295,314,315],{"class":301},"kind",[295,317,284],{"class":305},[295,319,320],{"class":308}," Role\n",[295,322,324,327],{"class":297,"line":323},3,[295,325,326],{"class":301},"metadata",[295,328,329],{"class":305},":\n",[295,331,333,336,338],{"class":297,"line":332},4,[295,334,335],{"class":301},"  name",[295,337,284],{"class":305},[295,339,340],{"class":308}," my-mvno-platform-license-agent\n",[295,342,344,347,349],{"class":297,"line":343},5,[295,345,346],{"class":301},"  namespace",[295,348,284],{"class":305},[295,350,351],{"class":308}," my-mvno\n",[295,353,355,358],{"class":297,"line":354},6,[295,356,357],{"class":301},"rules",[295,359,329],{"class":305},[295,361,363,366,369,371,374,377],{"class":297,"line":362},7,[295,364,365],{"class":305},"  -",[295,367,368],{"class":301}," apiGroups",[295,370,284],{"class":305},[295,372,373],{"class":305}," [",[295,375,376],{"class":305},"\"\"",[295,378,379],{"class":305},"]\n",[295,381,383,386,388,390,393,396,398],{"class":297,"line":382},8,[295,384,385],{"class":301},"    resources",[295,387,284],{"class":305},[295,389,373],{"class":305},[295,391,392],{"class":305},"\"",[295,394,395],{"class":308},"secrets",[295,397,392],{"class":305},[295,399,379],{"class":305},[295,401,403,406,408,410,412,415,417],{"class":297,"line":402},9,[295,404,405],{"class":301},"    resourceNames",[295,407,284],{"class":305},[295,409,373],{"class":305},[295,411,392],{"class":305},[295,413,414],{"class":308},"my-mvno-platform-env",[295,416,392],{"class":305},[295,418,379],{"class":305},[295,420,422,425,427,429,431,434,436,439,442,445,447],{"class":297,"line":421},10,[295,423,424],{"class":301},"    verbs",[295,426,284],{"class":305},[295,428,373],{"class":305},[295,430,392],{"class":305},[295,432,433],{"class":308},"get",[295,435,392],{"class":305},[295,437,438],{"class":305},",",[295,440,441],{"class":305}," \"",[295,443,444],{"class":308},"patch",[295,446,392],{"class":305},[295,448,379],{"class":305},[295,450,452,454,456,458,460,462,465,467],{"class":297,"line":451},11,[295,453,365],{"class":305},[295,455,368],{"class":301},[295,457,284],{"class":305},[295,459,373],{"class":305},[295,461,392],{"class":305},[295,463,464],{"class":308},"coordination.k8s.io",[295,466,392],{"class":305},[295,468,379],{"class":305},[295,470,472,474,476,478,480,483,485],{"class":297,"line":471},12,[295,473,385],{"class":301},[295,475,284],{"class":305},[295,477,373],{"class":305},[295,479,392],{"class":305},[295,481,482],{"class":308},"leases",[295,484,392],{"class":305},[295,486,379],{"class":305},[295,488,490,492,494,496,498,501,503],{"class":297,"line":489},13,[295,491,424],{"class":301},[295,493,284],{"class":305},[295,495,373],{"class":305},[295,497,392],{"class":305},[295,499,500],{"class":308},"create",[295,502,392],{"class":305},[295,504,379],{"class":305},[295,506,508,510,512,514,516,518,520,522],{"class":297,"line":507},14,[295,509,365],{"class":305},[295,511,368],{"class":301},[295,513,284],{"class":305},[295,515,373],{"class":305},[295,517,392],{"class":305},[295,519,464],{"class":308},[295,521,392],{"class":305},[295,523,379],{"class":305},[295,525,527,529,531,533,535,537,539],{"class":297,"line":526},15,[295,528,385],{"class":301},[295,530,284],{"class":305},[295,532,373],{"class":305},[295,534,392],{"class":305},[295,536,482],{"class":308},[295,538,392],{"class":305},[295,540,379],{"class":305},[295,542,544,546,548,550,552,555,557],{"class":297,"line":543},16,[295,545,405],{"class":301},[295,547,284],{"class":305},[295,549,373],{"class":305},[295,551,392],{"class":305},[295,553,554],{"class":308},"my-mvno-platform-license-agent",[295,556,392],{"class":305},[295,558,379],{"class":305},[295,560,562,564,566,568,570,572,574,576,578,581,583],{"class":297,"line":561},17,[295,563,424],{"class":301},[295,565,284],{"class":305},[295,567,373],{"class":305},[295,569,392],{"class":305},[295,571,433],{"class":308},[295,573,392],{"class":305},[295,575,438],{"class":305},[295,577,441],{"class":305},[295,579,580],{"class":308},"update",[295,582,392],{"class":305},[295,584,379],{"class":305},[146,586,587],{},"Read it rule by rule.",[146,589,590,593,594,596,597,599,600,603,604,607,608,611,612,614,615,617,618,620],{},[174,591,592],{},"Secrets."," ",[182,595,433],{}," and ",[182,598,444],{},", scoped by ",[182,601,602],{},"resourceNames"," to the single platform Secret. It cannot ",[182,605,606],{},"list"," Secrets, so it cannot enumerate what else is in your namespace. It cannot ",[182,609,610],{},"watch"," them. It cannot ",[182,613,500],{}," them, and that omission is deliberate: ",[182,616,500],{}," cannot be scoped by ",[182,619,602],{},", so granting it would widen this Role to every Secret in the namespace. The platform Secret already exists by the time the agent runs, so read-then-patch is all it ever needs.",[146,622,623,626,627,629,630,596,632,634,635,637,638,640,641,643],{},[174,624,625],{},"Leases."," The agent holds a ",[182,628,464],{}," Lease and only runs its renew loop while it holds it, which is how the chart guarantees exactly one renewer even if something starts a second copy. ",[182,631,433],{},[182,633,580],{}," are scoped by name to that one Lease, which is named after the agent's own Deployment. ",[182,636,500],{}," is the one verb that is not name-scoped, for the same reason it is absent above: ",[182,639,602],{}," does not apply to ",[182,642,500],{},", and the Lease does not exist yet on a first install, so the agent has to be able to make it once. The blast radius of that verb is the ability to create a coordination Lease in its own namespace.",[146,645,646],{},"That is the complete list. No pods, no exec, no nodes, no ConfigMaps, no other namespaces, nothing cluster-scoped. You can verify it yourself:",[286,648,652],{"className":649,"code":650,"language":651,"meta":291,"style":291},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","kubectl -n my-mvno auth can-i --list \\\n  --as=system:serviceaccount:my-mvno:my-mvno-platform-license-agent\n","bash",[182,653,654,679],{"__ignoreMap":291},[295,655,656,660,663,666,669,672,675],{"class":297,"line":298},[295,657,659],{"class":658},"sBMFI","kubectl",[295,661,662],{"class":308}," -n",[295,664,665],{"class":308}," my-mvno",[295,667,668],{"class":308}," auth",[295,670,671],{"class":308}," can-i",[295,673,674],{"class":308}," --list",[295,676,678],{"class":677},"sTEyZ"," \\\n",[295,680,681],{"class":297,"line":312},[295,682,683],{"class":308},"  --as=system:serviceaccount:my-mvno:my-mvno-platform-license-agent\n",[163,685,687],{"id":686},"traffic-goes-one-way","Traffic goes one way",[146,689,690],{},"Your deployment calls the Hub. Three kinds of call, all HTTPS, all outbound, all from inside your namespace:",[692,693,694,697,700],"ul",{},[153,695,696],{},"Activation, once, at enrollment.",[153,698,699],{},"Renewal, on a schedule.",[153,701,702,703,705],{},"The heartbeat, on a schedule. ",[213,704,82],{"href":83}," lists every field it carries.",[146,707,708],{},"The Hub does not call you. There is no inbound webhook, no callback, no port you open for us, no credential we hold for your cluster, no ingress rule you add on our behalf. If the Hub wanted to reach into your cluster, it has nothing to reach with.",[146,710,711,712,229],{},"Revocation runs on the same one-way rail. We change a row on our side, and your deployment learns about it the next time it calls us. See ",[213,713,86],{"href":87},[163,715,717],{"id":716},"the-hub-you-enroll-against-is-pinned","The Hub you enroll against is pinned",[146,719,720,723,724,726],{},[182,721,722],{},"activation.parentUrl"," is the Hub your deployment talks to, and the activation code carries a fingerprint of that Hub's signing key. Before the agent redeems a code it fetches the key set from ",[182,725,722],{}," and checks the fingerprint matches. A mismatch is refused.",[146,728,729,730,732],{},"This holds on the recovery path too, not only on first install, which is the path an operator uses under pressure. Pointing a deployment at a different ",[182,731,722],{}," with a code minted by us does not enroll it there.",[146,734,735,736,739,740,743,744,747,748,751,752,755],{},"The chart still exposes ",[182,737,738],{},"activation.allowUnpinned",", and in a released build that value does nothing. It is not a runtime toggle whose safety rests on nobody flipping it. The constant that gates the unpinned branch is compiled to ",[182,741,742],{},"false"," behind a ",[182,745,746],{},"!dev"," build tag, so in any binary we ship the branch is dead code and an unpinned code is refused whatever the value, the env var, or your ",[182,749,750],{},"values.yaml"," says. The escape hatch only exists in a binary built with the ",[182,753,754],{},"dev"," tag, for local work against a throwaway Hub, and that is not the binary in the image you pulled. Nothing an operator can set at install time reaches it.",[757,758,759],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":291,"searchDepth":298,"depth":312,"links":761},[762,763,764,765,766],{"id":165,"depth":312,"text":166},{"id":203,"depth":312,"text":204},{"id":272,"depth":312,"text":273},{"id":686,"depth":312,"text":687},{"id":716,"depth":312,"text":717},"The signing key is generated in your cluster and never transmitted, and the license agent can touch exactly two objects in your namespace.","md",null,{},true,{"title":78,"description":767},"HTUQOlXKS1WC2AVk0B-wBngoq2FPVlW72tf_EHTgWOc",[775,777],{"title":74,"path":75,"stem":76,"description":776,"children":-1},"The full lifecycle of your license, from the activation code you paste into helm install to the renewals that keep your deployment serving.",{"title":82,"path":83,"stem":84,"description":778,"children":-1},"Every field your deployment sends to the Hub, why it sends it, and what it never sends.",1783967741116]