Skip to main content

Install Tenon Licensing Server


The Tenon licensing server is deployed via a Helm chart. This guide walks through the full two-phase provisioning flow: the chart installs and generates the in-enclave keypair, then you have Tangram Flex KMS-sign a delegation over that public key and apply it as a Kubernetes Secret.

note

For a full list of configurable chart values see the Tenon Licensing Server Chart Values.

Prerequisites​

Infrastructure​

The chart is compatible with any Kubernetes cluster meeting the following requirements:

  • Kubernetes v1.23 or above
  • A container runtime capable of running linux/amd64 images
  • Persistent volume support (for the Valkey seat-state store and the activityd activity store)

For a minimal single-node deployment (dev/test):

  • 2 vCPU, 4 GB RAM, 20 GB storage

For a production deployment with replicaCount: 2 for licensed:

  • 4 vCPU, 8 GB RAM, 50 GB storage

Tools​

  • Helm v3 — helm version
  • kubectl — kubectl version
  • Access to the Tangram Flex GitLab container registry (registry-gitlab.tangramflex.tech)

Credentials and materials​

Before you begin, you will need:

  • Registry credentials — provided by Tangram Flex for pulling the licensed and dashboard images
  • Delegation JSON + root public key — provided by Tangram Flex after Phase 1 (see Phase 2 below)

Installation​

The deployment is intentionally two-phase to keep the in-enclave delegate private key inside your cluster at all times:

  1. Phase 1 — helm install: deploys all components and runs the keygen hook, which generates the delegate keypair inside your cluster. licensed pods wait for the trust Secret.
  2. Phase 2 — apply trust: you extract the delegate public key, send it to Tangram Flex, receive a KMS-signed delegation.json and root-pub.pem, and apply them as a Secret. licensed starts automatically.

Phase 1 — Helm install​

1. Prepare your session​

export TENON_NS="tenon-licensing"
export TENON_RELEASE="flex-license"
export TENON_VALUES_FILE="tenon_licensing_values.yaml"

2. Add the Helm chart repository​

helm repo add flex-licensing \
  "https://gitlab.tangramflex.tech/api/v4/projects/<PROJECT_ID>/packages/helm/stable"
helm repo update
note

Replace <PROJECT_ID> with the GitLab project ID provided by Tangram Flex. You can also obtain it from the project's GitLab page.

3. Create the namespace​

kubectl create ns "$TENON_NS"

4. Create the registry pull Secret​

The licensed and dashboard images are hosted in the Tangram Flex GitLab container registry. Create a Kubernetes image pull Secret with the credentials provided by Tangram Flex:

export REGISTRY_SECRET_NAME="flex-licensing-registry"
export REGISTRY_SERVER="registry-gitlab.tangramflex.tech"
export REGISTRY_USERNAME="<usernameProvidedByTangramFlex>"
export REGISTRY_PASSWORD="<passwordProvidedByTangramFlex>"

kubectl create secret docker-registry "$REGISTRY_SECRET_NAME" \
  --docker-server="$REGISTRY_SERVER" \
  --docker-username="$REGISTRY_USERNAME" \
  --docker-password="$REGISTRY_PASSWORD" \
  --namespace="$TENON_NS"

Reference the Secret in your values override file:

global:
  imagePullSecrets:
    - flex-licensing-registry

5. Create a values override file​

Create tenon_licensing_values.yaml. At minimum, reference your pull Secret and set the image tags provided by Tangram Flex:

global:
  imagePullSecrets:
    - flex-licensing-registry   # matches the Secret created above

licensed:
  image:
    tag: "0.3.0"   # pinned to the version provided by Tangram Flex

dashboard:
  image:
    tag: "0.3.0"   # pinned to the version provided by Tangram Flex

6. Install the chart​

helm upgrade "$TENON_RELEASE" -n "$TENON_NS" flex-licensing/licensing-self-host \
  --install \
  -f "$TENON_VALUES_FILE"
Do not use --wait for initial installs

By default (trust.waitForTrust: true), the trust Secret volume is required, so licensed pods stay in ContainerCreating until Phase 2. Using --wait on a first install causes Helm to time out and mark the release as failed. Omit --wait for Phase 1. You can use it safely on upgrades after trust is already in place.

After install, confirm the keygen hook completed and the licensed pods are waiting:

kubectl -n "$TENON_NS" get pods -l app.kubernetes.io/instance="$TENON_RELEASE"
# licensed pods show ContainerCreating — expected until Phase 2

The keygen hook has already run and generated the in-enclave delegate keypair. Extract the public key:

kubectl -n "$TENON_NS" get configmap licensed-delegate-pub \
  -o jsonpath='{.data.delegate-pub\.pem}' > delegate-pub.pem

Send delegate-pub.pem to your Tangram Flex contact, along with:

  • Your organization name
  • The number of floating seats needed (max_seats)
  • The number of node-locked seats (if any)
  • The machine fingerprints for node-locked seats (see below)
  • The desired validity period (in days)
Node-lock fingerprints

For node-locked seats, each machine must provide its fingerprint. Run this on each machine that needs a node-locked seat:

tenon license fingerprint
# → 9118df5be61bf9f3f4fe49ffa94037d4

Send the hex strings to Tangram Flex and they will be baked into the signed delegation.


Phase 2 — Apply trust material​

Once Tangram Flex returns delegation.json and root-pub.pem, apply them as a Kubernetes Secret:

kubectl -n "$TENON_NS" create secret generic licensed-trust \
  --from-file=delegation.json \
  --from-file=root-pub.pem

The licensed pods detect the Secret and start automatically (no restart required). Verify:

kubectl -n "$TENON_NS" rollout status deploy/"${TENON_RELEASE}-licensing-self-host-licensed"
kubectl -n "$TENON_NS" get pods -l app.kubernetes.io/instance="$TENON_RELEASE"

Post-install​

Retrieve generated credentials​

The chart auto-generates an admin token (for reading activity data and accessing holder names) and, if dashboard auth is enabled, a dashboard admin password. Retrieve them after install:

# Admin token (for CLI and activityd access)
kubectl -n "$TENON_NS" get secret "${TENON_RELEASE}-licensing-self-host-admin-token" \
  -o jsonpath='{.data.token}' | base64 -d; echo

# Dashboard admin password (if dashboard.auth.enabled=true)
kubectl -n "$TENON_NS" get secret "${TENON_RELEASE}-licensing-self-host-dashboard-auth" \
  -o jsonpath='{.data.admin-password}' | base64 -d; echo

Access the operator dashboard​

The dashboard is a ClusterIP service by default. Port-forward to reach it locally:

kubectl -n "$TENON_NS" port-forward \
  svc/"${TENON_RELEASE}-licensing-self-host-dashboard" 8081:8080

Then open http://localhost:8081 in your browser.

To expose the dashboard via a standard Kubernetes Ingress, add the following to your values override file:

dashboard:
  ingress:
    enabled: true
    className: "nginx"        # your ingress controller's class
    annotations:
      cert-manager.io/cluster-issuer: my-issuer   # optional: cert-manager annotation
    hosts:
      - host: dashboard.licensing.example.mil
        paths:
          - path: /
            pathType: Prefix
    tls:
      - secretName: dashboard-tls
        hosts:
          - dashboard.licensing.example.mil

# Required when networkPolicy.enabled=true (the default):
# allow the ingress controller namespace to reach the dashboard pod.
networkPolicy:
  dashboardFromNamespaces:
    - ingress-nginx   # set this to your ingress controller's namespace
NetworkPolicy and ingress

The chart deploys a default-deny NetworkPolicy. When you expose the dashboard via ingress, the ingress controller pods are in a different namespace and are blocked by default, producing a 502 from the ingress controller. Set networkPolicy.dashboardFromNamespaces to the namespace where your ingress controller runs (e.g. ingress-nginx, nginx, traefik).

Dashboard authentication

The dashboard has no built-in TLS or authentication for incoming connections — viewing is always read-only without sign-in. Mutating actions (Release all seats, Restart) require the admin password. For production, terminate TLS and add network-level access controls at the ingress or service-mesh layer before exposing the dashboard outside your cluster.


Renewal​

Before the delegation's valid_until date lapses, request a renewed delegation.json from Tangram Flex (same in-enclave public key, new expiration date, possibly updated seat count). Swap the Secret and roll the deployment — no image rebuild or API connectivity required:

kubectl -n "$TENON_NS" delete secret licensed-trust
kubectl -n "$TENON_NS" create secret generic licensed-trust \
  --from-file=delegation.json \
  --from-file=root-pub.pem

kubectl -n "$TENON_NS" rollout restart \
  deploy/"${TENON_RELEASE}-licensing-self-host-licensed"

Upgrade​

To upgrade the chart to a new version:

helm repo update

helm upgrade "$TENON_RELEASE" -n "$TENON_NS" flex-licensing/licensing-self-host \
  --install \
  --wait --timeout 5m \
  -f "$TENON_VALUES_FILE"

The keygen hook is idempotent — it will not rotate the existing in-enclave delegate keypair on upgrade.


Air-gapped (registry mirror)​

If your enclave cannot reach registry-gitlab.tangramflex.tech, mirror the images to your internal registry and set the override in your values file:

global:
  imageRegistry: "your.internal.registry.mil"

This replaces the default registry prefix for all images in the chart (including the bundled Valkey image).


External Valkey​

The chart includes a bundled single-node Valkey instance suitable for most deployments. To use an external or managed Redis-compatible instance instead:

valkey:
  enabled: false
  externalAddr: "valkey.example.mil:6379"
  existingPasswordSecret: "valkey-auth"      # optional: Secret with key 'password'
  existingPasswordSecretKey: password