Document provider-MSP deploy bundle as canonical install; surface lease signing public key in setup.sh

- MSP.md now leads with deploy/provider-msp/ (compose stack, setup.sh,
  upgrade.sh, run-install-proof.sh), documents the HTTPS requirement
  (__Host- portal session cookie) and the pulse.provider-msp.role labels
  workspace provisioning requires, and explains the licence/lease chain
  including licence-expiry behavior.
- setup.sh derives and prints the lease signing public key the provider
  MSP licence must bind (also via --print-lease-signing-public-key), and
  the missing-licence error now includes it with request instructions.
- .env.example documents the CP_TRIAL_ACTIVATION_PRIVATE_KEY binding.
This commit is contained in:
rcourtman
2026-06-10 14:00:11 +01:00
parent d25b99cbcc
commit 2482d4acb6
3 changed files with 101 additions and 3 deletions
+6
View File
@@ -26,6 +26,12 @@ PULSE_PROVIDER_MSP_ROOT_SPACECHECK_DIR=/var/lib/pulse-provider-msp/spacecheck/ro
PULSE_PROVIDER_MSP_DOCKER_SPACECHECK_DIR=/var/lib/docker/.pulse-provider-msp-spacecheck
CP_TRUSTED_PROXY_CIDRS=172.30.0.0/24
CP_PROVIDER_MSP_LICENSE_FILE=./provider-msp-license.jwt
# Entitlement lease signing key. setup.sh generates this; the private key
# never leaves this host. Your provider MSP license must bind the derived
# PUBLIC key (entitlement_signing_public_key) — print it with
# `./setup.sh --print-lease-signing-public-key` and include it in your
# license request. The control plane refuses to start if license and key
# do not match.
CP_TRIAL_ACTIVATION_PRIVATE_KEY=
CP_TENANT_MEMORY_LIMIT=536870912
CP_ALLOW_DOCKERLESS_PROVISIONING=false
+55 -1
View File
@@ -297,6 +297,42 @@ ensure_generated_secrets() {
chmod 0600 "${env_path}"
}
# derive_lease_signing_public_key prints the base64 Ed25519 public key for
# CP_TRIAL_ACTIVATION_PRIVATE_KEY. The provider MSP license must bind this
# exact key (entitlement_signing_public_key) or the control plane will refuse
# to start; include it when requesting your license. The private key never
# leaves this host.
derive_lease_signing_public_key() {
local env_path="${PULSE_PROVIDER_MSP_INSTALL_DIR}/.env"
[[ -f "${env_path}" ]] || die "missing ${env_path}"
have openssl || die "openssl is required to derive the lease signing public key"
local key_b64 key_len tmp_der
key_b64="$(env_value CP_TRIAL_ACTIVATION_PRIVATE_KEY "${env_path}")"
[[ -n "${key_b64}" ]] || die "CP_TRIAL_ACTIVATION_PRIVATE_KEY is not set; run setup.sh first"
key_len="$(printf '%s' "${key_b64}" | base64 -d 2>/dev/null | wc -c | tr -d ' ')"
case "${key_len}" in
64)
# 64-byte Ed25519 private key: the public key is the trailing 32 bytes.
printf '%s' "${key_b64}" | base64 -d | tail -c 32 | base64 | tr -d '\n'
;;
32)
# 32-byte seed: wrap in a PKCS#8 DER envelope and let openssl derive
# the public key (raw key = trailing 32 bytes of the SPKI DER).
tmp_der="$(mktemp)"
{
printf '\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x70\x04\x22\x04\x20'
printf '%s' "${key_b64}" | base64 -d
} >"${tmp_der}"
openssl pkey -inform DER -in "${tmp_der}" -pubout -outform DER 2>/dev/null | tail -c 32 | base64 | tr -d '\n'
rm -f "${tmp_der}"
;;
*)
die "CP_TRIAL_ACTIVATION_PRIVATE_KEY must decode to a 32-byte seed or 64-byte Ed25519 key (got ${key_len} bytes)"
;;
esac
}
truthy() {
case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in
true|1|yes|on) return 0 ;;
@@ -454,7 +490,13 @@ validate_env_file() {
if [[ "${license_file}" != /* ]]; then
license_file="${PULSE_PROVIDER_MSP_INSTALL_DIR}/${license_file}"
fi
[[ -f "${license_file}" ]] || die "CP_PROVIDER_MSP_LICENSE_FILE does not exist: ${license_file}"
if [[ ! -f "${license_file}" ]]; then
die "CP_PROVIDER_MSP_LICENSE_FILE does not exist: ${license_file}
Request your provider MSP license with this lease signing public key
(./setup.sh --print-lease-signing-public-key):
$(derive_lease_signing_public_key)
The license must bind this key or the control plane will refuse to start."
fi
}
validate_compose_config() {
@@ -521,12 +563,24 @@ Proof:
Portal:
https://${domain}/
Lease signing public key (your provider MSP license must bind this key;
re-print any time with ./setup.sh --print-lease-signing-public-key):
$(derive_lease_signing_public_key)
EOF
}
main() {
need_root
if [[ "${1:-}" == "--print-lease-signing-public-key" ]]; then
ensure_env_file
ensure_generated_secrets
derive_lease_signing_public_key
printf '\n'
exit 0
fi
log "starting provider MSP first-time setup"
apt_install apt-transport-https
install_docker_ce
+40 -2
View File
@@ -11,8 +11,23 @@ alert routing, and reporting. It assumes you have read
runtime per client workspace. Alerts, webhook destinations, branded report
settings, users, audit history, and metrics stay inside the client runtime;
duplicate hostnames across clients never collide because they never share a
runtime namespace. The stack is operated with the `pulse-control-plane`
binary:
runtime namespace.
The canonical install is the deploy bundle at
[`deploy/provider-msp/`](../deploy/provider-msp/): a Docker Compose stack
(Traefik ingress with wildcard TLS, a hardened Docker socket proxy, and the
control plane), a guided `setup.sh` for fresh hosts, `upgrade.sh` for
backup-gated upgrades, and `run-install-proof.sh` for an end-to-end fresh
install proof. `.env.example` in that directory doubles as the operator
runbook. Start there rather than wiring containers by hand; among other
things the compose stack provides the `pulse.provider-msp.role=traefik` and
`pulse.provider-msp.role=control-plane` container labels that client
workspace provisioning requires for isolated tenant networking, and it
terminates TLS — the management portal sets a `__Host-` (HTTPS-only) session
cookie, so the portal does not work over plain HTTP.
Day-2 operations run through the `pulse-control-plane` binary (via
`docker compose run --rm control-plane …` in the bundle):
```bash
pulse-control-plane provider-msp bootstrap --account-name "Your MSP" --owner-email you@example.com
@@ -162,3 +177,26 @@ are carried on the licence key. MSP plans are sized by client workspace count
(Starter 5, Growth 15, Scale 40); workspace creation is blocked, not billed,
when the limit is reached. MSP and Enterprise keys are issued through sales —
contact support to get set up or to join the MSP design-partner program.
In the provider-hosted model the licence is a signed file
(`CP_PROVIDER_MSP_LICENSE_FILE`) that also binds your control plane's
entitlement lease signing key:
1. `setup.sh` generates `CP_TRIAL_ACTIVATION_PRIVATE_KEY` locally; the
private key never leaves your host.
2. Send the derived public key
(`./setup.sh --print-lease-signing-public-key`) with your licence request.
3. The issued licence binds that key. The control plane refuses to start in
provider mode if the licence and key do not match, so a misconfigured
stack fails at startup instead of provisioning client workspaces that
silently run unlicensed.
Client runtimes lease their entitlements from your control plane (the
control plane injects the refresh endpoint; nothing phones Pulse Cloud) and
verify each lease through the licence chain: Pulse's embedded key signs your
licence, your licence binds your signing key, your signing key signs the
lease. Leases carry the MSP capability set plus `white_label`, so branded
per-client reports work inside every client workspace. When the licence
expires, leases stop verifying after the grace period and client runtimes
fall back to Community behavior; renew and restart the control plane to
restore them.